Rust 기본 개념 - 구조체
구조체는 타입이 다른 여러 값을 하나로 묶는 방법 입니다.
구조체는 설명만 보면 튜플과 상당히 비슷해 보입니다. 실제로도 튜플처럼 구조체의 구성요소들을 각각 다른 타입이 될 수 있습니다.
그리고 구조체는 각각의 구성 요소에 이름을 붙일 수 있습니다.
객체지향 언어를 해보았던 사람이라면 바로 객체가 떠오를겁니다.
아래를 보면 우리가 생각한 객체의 모습과 상당히 비슷합니다.
자바에서는 아마 이런 구조를 객체 또는 클래스 라고 불렀던 것 같습니다.
타입스크립트 에서는 타입 혹은 인터페이스가 구조체와 비슷하다고 볼 수 있겠네요!
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
정의한 구조체는 해당 구조체의 각 필드에 대한 구체적인 값을 정해서 인스턴스화 합니다
인스턴스란화? 데이터의 구조 혹은 구조체 같은 것을 실제 데이터화 하여 메모리에 할당 하는 것을 말합니다.
fn main(){
let user1 = User {
active: true,
username: String::from("user123"),
email: String::from("user123@example.com"),
sign_in_count: 1,
};
}
이 구조체를 인스턴스화 한뒤 이 값에 접근 하려면 점(.) 표기법을 사용합니다.
fn main(){
let user1 = User {
active: true,
username: String::from("user123"),
email: String::from("user123@example.com"),
sign_in_count: 1,
};
// user1 인스턴스의 email 필드 값을 바꾼다.
user1.email = String::from("test123@example.com");
}
각 필드별로 가변성을 지정할 수는 없어서 자바의 클래스와는 조금 다르다는 것을 알 수 있습니다.
자바에서는 각 필드마다 final이라는 키워드로 읽기전용으로 만들 수 있습니다.
구조체와 소유권
구조체 또한 러스트의 소유권 규칙을 따릅니다.
아래 구조체를 보면 username,email 필드의 값이 String 타입으로 정의되어있습니다.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
&str 문자열 슬라이스도 있는데 왜 String을 사용했을까요?
인스턴스가 유효한 동안 각 인스턴스 내의 모든 데이터가 유효하도록 하기 위한 의도한 방법입니다.
소유권을 갖지 않은 문자열 슬라이스를 사용했다면 아마 컴파일러에서 에러를 발생시켰을 겁니다.
구조체에서 문자열 슬라이스를 사용하려면 라이프타임을 명시해주어야 합니다.
구조체의 활용
아래는 구조체 없이 함수의 인수를 각각 받아서 결과를 반환하는 코드 입니다.
두가지 인수를 받고있는데 이런경우 코드를 처음 보는 사람은 두 인수가 서로 연관이 있는 인수라는걸 한눈에 알기 어렵습니다.
( 물론 아래 코드는 너무 간단해서 바로 이해가 가능합니다 )
fn main() {
let width1 = 30;
let height1 = 50;
println!(
"Result: {}", area(width1, height1));
}
fn area(width: u32, height:u32) -> u32 {
width * height
}
이런경우 객체지향 언어에서는 함수에서 받는 인수를 객체 혹은 클래스로 미리 정의합니다.
JAVA
class Dimension {
int width;
int height;
Dimension(int width, int height) {
this.width = width;
this.height = height;
}
}
public class Main {
// Dimension 객체를 인수로 받는 메서드
static void printDimension(Dimension dim) {
System.out.println("Width: " + dim.width + ", Height: " + dim.height);
}
public static void main(String[] args) {
Dimension dim = new Dimension(1920, 1080); // width와 height를 하나로 묶음
printDimension(dim); // 메서드에 단일 인수 전달
}
}
TypeScript
// Dimension 타입 정의
type Dimension = {
width: number;
height: number;
};
// Dimension 객체를 인수로 받는 함수
function printDimension(dim: Dimension): void {
console.log(`Width: ${dim.width}, Height: ${dim.height}`);
}
// 사용 예시
const dim = { width: 1920, height: 1080 };
printDimension(dim);
한눈에 보기에도 서로 연관이 있다는 것을 알 수 있습니다.
Rust에서는 튜플, 구조체 두가지 방식으로 이를 처리 할 수 있습니다.
튜플
fn main() {
let rect1 = (30,50);
println!(
"Result: {}", area(rect1));
}
fn area(dimensions:(u32,u32)) -> u32 {
// dimensions.0,dimensions.1이 구체적으로 뭘 의미하는지 모름
dimensions.0 * dimensions.1
}
깔끔해지긴 했지만 area 함수에 약간 문제가 있어보입니다.
튜플의 특성 때문에 값을 인덱스로 접근해야 하는데 이로인해 해당 인덱스의 값이 가로인지 세로인지 아니면 무슨 다른 값인지 오히려 더 알기가 어려워졌습니다.
구조체
struct Rect {
width: u32,
height: u32
}
fn main() {
let rect1 = Rect{
width:30,
height:50
};
println!(
"Result: {}", area(rect1));
}
fn area(dimensions:Rect) -> u32 {
dimensions.width * dimensions.height
}
훨씬 나은 것 같습니다. area 함수가 어떤 값을 어떻게 처리하는지 확실하게 알 수 있네요.
디버깅
구조체를 이제 활용까지 할 수 있으니 디버깅도 할 줄 알아야 할겁니다.
자바스크립트에서는 객체를 출력하면 별다른 설정 없이 해당 객체 모양으로 바로 출력이 됩니다.
하지만 JAVA와 Rust에서는 그렇지 않습니다. 우선 JAVA는 객체를 출력하면 클래스 이름과 해시코드를 출력하고, Rust는 컴파일러가 에러를 발생 시킵니다.
JAVA에서는 toString 메서드를 재정의 해서 사용하거나 별도의 메서드를 만들어서 객체 출력을 처리합니다.
예 짜증나고 귀찮습니다.
다행스럽게도 Rust는 딸깍 한번으로 해결이 가능합니다.
다만 구조체에서는 Display 구현체가 기본 제공되지 않기 때문에 {:?} 를 사용해야 합니다
왜냐구요?
컴파일러가 그렇게 하랍니다.
= help: the trait `std::fmt::Display` is not implemented for `Rect`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
하라는대로 했더니 또 컴파일러가 막 혼냅니다.
Rect가 Debug를 구현하지 않았답니다.
뭔가 에러만 보면 JAVA 에서 추상 클래스에 정의한 메서드를 제대로 구현하지 않은듯한 그런 느낌이 듭니다
알고보니 Rust는 디버깅 정보를 출력하는 기능을 자체적으로 가지고 있기는 합니다만 명시적 동의가 필요하답니다.
error[E0277]: `Rect` doesn't implement `Debug`
#[derive(Debug)] 라는 외부 속성을 작성해주면 된다고 합니다.
// 명시적 동의
#[derive(Debug)]
struct Rect {
width: u32,
height: u32
}
fn main() {
let rect1 = Rect{
width:30,
height:50
};
println!(
"Result: {:?}", rect1);
}
오.. 잘나옵니다.
근데 뭔가 시각적으로 깔끔하진 않네요
이럴 땐 {:#?} 라는걸 쓰면 깔끔해집니다.
오..
다음 글은 메서드가 될 것 같습니다.