🏗️

구조체와 impl — Ruby의 class가 Rust에서 두 조각으로 나뉜다

struct로 데이터, impl로 메서드, trait로 다형성

Ruby에서 클래스를 만들면 데이터(인스턴스 변수)와 메서드가 한 몸이다. Rust는 이걸 분리했다.

# Ruby — 데이터와 메서드가 한 몸
class User
  attr_reader :name, :email
  def initialize(name, email)
    @name = name
    @email = email
  end
  def display
    "#{name} <#{email}>"
  end
end

// Rust — 데이터
struct User {
    name: String,
    email: String,
}

// Rust — 메서드 (별도 블록)
impl User {
    fn new(name: String, email: String) -> Self {
        User { name, email }
    }
    fn display(&self) -> String {
        format!("{} <{}>", self.name, self.email)
    }
}

&self — Ruby의 암시적 self가 명시적으로

Ruby에서 인스턴스 메서드 안에서 self는 자동으로 쓸 수 있다. name만 써도 self.name이다. Rust에서는 첫 번째 인자로 &self를 명시해야 한다.

  • &self — 읽기 전용 (Ruby의 일반 메서드)

  • &mut self — 값을 변경할 수 있음 (Ruby의 ! 메서드와 비슷)

  • self — 소유권을 가져감 (호출 후 원본 사용 불가)

impl User {
    fn name(&self) -> &str { &self.name }           // 읽기
    fn set_name(&mut self, name: String) { self.name = name; } // 쓰기
    fn into_name(self) -> String { self.name }        // 소유권 이동
}

associated function — Ruby의 클래스 메서드

&self를 안 받는 함수는 Ruby의 클래스 메서드에 해당한다.

# Ruby
User.create(name: "sehwa")  # 클래스 메서드

// Rust
User::new(String::from("sehwa"), String::from("s@e.com"));  // :: 로 호출

.이 아니라 ::로 호출한다. new는 특별한 키워드가 아니라 그냥 관례다.

trait — Ruby의 module

Ruby에서 include로 모듈을 섞어 넣듯, Rust에서는 traitimpl한다.

# Ruby
module Printable
  def print_info
    puts to_s
  end
end
class User
  include Printable
end

// Rust
trait Printable {
    fn print_info(&self);
}

impl Printable for User {
    fn print_info(&self) {
        println!("{}", self.display());
    }
}

핵심 차이: Ruby의 module은 구현을 포함할 수 있고 다중 include가 자유롭다. Rust의 trait은 인터페이스에 가깝고, 구현은 impl Trait for Type에서 한다. 다이아몬드 상속 문제가 없다.

derive — 자동 구현

Ruby에서 Comparable을 include하면 <=>만 정의하면 나머지 비교 연산자가 자동으로 생긴다. Rust에서는 derive로 비슷한 걸 한다.

#[derive(Debug, Clone, PartialEq)]
struct User {
    name: String,
    email: String,
}

Debugputs user.inspect 같은 디버그 출력, Clone.dup, PartialEq== 비교. 어노테이션 한 줄로 자동 구현된다.

상속이 없다

Rust에는 상속이 없다. 이게 Ruby 개발자에게 가장 큰 패러다임 전환이다. 코드 재사용은 composition(구조체 안에 다른 구조체를 넣는 것)이나 trait으로 해결한다.

핵심 포인트

1

struct로 데이터 정의, impl로 메서드 정의 — 별도 블록

2

&self = 읽기, &mut self = 쓰기, self = 소유권 이동

3

trait은 Ruby의 module include에 대응 — 상속 대신 사용

4

derive로 Debug, Clone, PartialEq 등을 자동 구현

장점

  • 데이터와 행위가 분리되어 구조가 명확하다
  • trait은 다이아몬드 상속 문제가 없다

단점

  • Ruby의 open class(기존 클래스에 메서드 추가)가 안 된다
  • 상속 없이 코드 재사용하는 사고방식 전환이 필요하다

사용 사례

ActiveRecord 모델처럼 데이터 + 비즈니스 로직 묶을 때 Ruby의 Comparable/Enumerable을 trait으로 구현할 때