[Rust] 구조체

MS Choi·2022년 7월 6일
0

Rust Study

목록 보기
3/4

C, C++를 주로 다루었던 개발자라면 매우 익숙한 개념이라고 할 수 있다. Rust의 구조체는 앞에서 말한 기존의 구조체와 어떤 차이가 있는지 한번 알아보자.

먼저 C, C++의 구조체의 정의이다.

struct User
{
	std::string userName;
    std::string email;
    unsigned int signInCount;
    bool active;
}

만약 이 구조체를 Rust로 선언하면 어떨까?

struct User {
	username: String,
    email: String,
    sign_in_count : u64,
    active : bool
}

정의를 하는건 기존의 구조체와 큰차이가 없는걸 확인 할 수 있다. 생성역시 기존의 언어들에서 사용하는 구조체와 큰 차이가 없다는걸 알 수 있는데

let user1 = User{
  email: String:from("someone@example.com"), 
  username: String:from("someoneusername123"), 
  active: true, 
  sign_in_account : 1,
};

이 역시도 기존의 구조체와 비슷하다. 큰 차이가 없으니, 필드값을 바꾸거나 생성자를 어떻게 만드는지는 빠르게 치나가 보겠다.

let user1 = User{
  email: String:from("someone@example.com"), 
  username: String:from("someoneusername123"), 
  active: true, 
  sign_in_account : 1,
};

user1.email = String::from("anotheremail@example.com);

<객체의 필드값 변경>

fn build_user(email:String, username: String) -> user {
	User {
    	email: email,
        username: username,
        active: true,
        sign_in_count: 1,
    }
}

<user의 객체를 반환하는 함수>

단축 문법을 사용하면 더 간단하게 표현도 가능하다

fn build_user(email:String, username: String) -> user {
	User {
    	email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

<전부 다 적은것과 같은 결과가 나온다>

Rust에는 기본적으론 Copy의 개념이 없다보니, 기존 구조체 인스턴스를 갱신해서 적용할 수 있는 문법이 존재한다.

let user1 = User{
  email: String:from("someone@example.com"), 
  username: String:from("someoneusername123"), 
  active: true, 
  sign_in_account : 1,
};

let user2 = User{
  email: String:from("other@example.com"), 
  username: String:from("otherusername123"), 
  active: user1.active, 
  sign_in_account : user1.sign_in_account,
};

//이런방식도 가능하다.

let user2 = User {
	 email: String:from("other@example.com"), 
  	 username: String:from("otherusername123"), 
     ..user1
};
//이렇게 하면 선언을 통해 값을 집어넣은 필드를 제외하곤 user1이 가진값이 채워진다.

Rust에서는 필드 이름이 없는 튜플 구조체도 만들 수가 있다. 이름을 정할 필요가 없는 간단한 데이터의 집합을 만들때 사용하면 매우 유용하다.

struct Color(i32,i32,i32);
struct Point(i32,i32,i32);

let black = Color(0,0,0);
let origin = Point(0,0,0);

여기서 주의할 점은 Color와 Point의 필드의 갯수가 같고 모든 타입이 같다고 해서 Color 객체를 인자로 필요로 하는 함수에 Point를 넘겨줄 수 없다는 것이다. 이 점을 제외하면 튜플 구조체는 튜플과 똑같이 작동한다.

어? C, C++은 구조체에 함수선언도 가능했는데...

Rust도 가능하다. 처음에는 선언 방식때문에 헷갈릴 수도 있으나, 개인적으론 이 방법이 더 편하다고 생각한다.

먼저 구조체에 선언할 수 있는건 메소드(Method)와 함수 2가지이다.

여기서 메소드(Method)가 무엇이냐면, 함수의 매개변수로 자신의 객체를 받는 특별한 함수를 메소드(Method)라 부른다. 이때 사용하는 예약어가 있는데 바로 self이다. 밑에 적은 예시를 한번 보자

struct Rectangle{
    width : u32,
    height : u32,
}
//구조체에 대한 함수는 여기서 정의
impl Rectangle {
//메서드는 이렇게 구현한다.
    fn get_area(&self) -> u32 {
        self.width * self.height
    }
//이것도 메서드...
    fn can_hold(&self, other: &Rectangle) -> bool {
        return self.width>other.width && self.height> other.height
    }
}

self를 사용할 경우 매개변수 타입을 따로 명시하지 않아도 괜찮다. 자기자신을 가리키기 때문에 타입을 알려줄 필요가 없기 때문이다.

이렇게 self매개변수를 사용하는 함수를 Method라고 한다.

그럼 self 함수를 사용하지 않아도 선언이 가능한가요?

물론 가능하다. 우리는 self를 사용하지 않고 정의되는 함수를 연관 함수라고 부른다.

impl Rectangle{
//보통 생성자를 선언할때 많이 쓰인다.
	fn square(size:u32) -> Rectangle{
		Rectangle{width: size, height: size}
    }
}

//연관함수 호출은 이렇게 한다. String::from도 연관함수이기 때문에 이것과 같다.
let rect = Rectangle::square(10);

::(콜론)을 사용하는 문법은 연관함수를 호출하는것 뿐만아니라, namespace를 호출할때도 사용되기 때문에 기억하고 있으면 편하다.

자 여기서 문제이다. 연관함수와 메서드를 각각 다른 impl블록에 선언했다. 이때 동작에 차이가 있을까?

차이는 없다. 분리해서 구현해도 한번에 다 적는것과 완전히 똑같이 동작한다. 지금 당장은 이걸 알 필요는 없지만, 나중에 제너릭이랑 트레이트를 구현할때 유용하게 사용된다는것을 기억해두자

다음은?

이제 구조체를 배웠으니, 열거형, 클래스 순으로 객체지향 프로그래밍과 관련된 부분들을 쭉 공부할 예정이다.

profile
다양한 경험을 하는걸 좋아하는 개발자입니다.

0개의 댓글