함수와 메서드 — def가 fn이 되면 뭐가 달라지는가
반환 타입, 세미콜론, 그리고 Ruby와 같은 "마지막 식이 반환값" 규칙
Ruby에서 함수를 정의하면 인자 타입도, 반환 타입도 안 쓴다. Rust는 둘 다 명시해야 한다.
# Ruby
def greet(name)
"Hello, #{name}!"
end
// Rust
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
-> 뒤에 반환 타입을 쓴다. 반환값이 없으면(Ruby의 nil 반환) -> ()이지만 생략 가능.
세미콜론 = 반환값 여부
Ruby와 Rust 모두 "마지막 표현식이 반환값"이라는 규칙이 있다. 근데 Rust에서는 세미콜론을 붙이면 반환이 아니라 statement가 된다.
fn add(a: i32, b: i32) -> i32 {
a + b // 세미콜론 없음 → 반환값
}
fn add_broken(a: i32, b: i32) -> i32 {
a + b; // 세미콜론 있음 → statement, ()를 반환 → 컴파일 에러!
}
이거 처음에 진짜 헷갈린다. Ruby에서는 세미콜론이 문장 구분자일 뿐 의미가 없는데, Rust에서는 반환 여부를 결정한다.
early return
Ruby처럼 return 키워드로 일찍 빠져나올 수 있다.
fn check_age(age: u32) -> &'static str {
if age < 18 {
return "minor"; // early return에는 세미콜론 필요
}
"adult" // 마지막 표현식은 세미콜론 없이
}
return을 쓸 때는 세미콜론을 붙인다. 마지막 표현식으로 반환할 때는 안 붙인다. 이 패턴이 Rust의 관례.
인자 전달 — 값 vs 참조
Ruby에서는 인자 전달 방식을 신경 쓸 일이 없다. Rust에서는 소유권 문제 때문에 빌려줄지(borrow), 넘겨줄지(move)를 결정해야 한다.
// 빌림 — 원본 유지
fn print_name(name: &str) {
println!("{}", name);
}
// 소유권 이동 — 호출 후 원본 사용 불가
fn take_name(name: String) {
println!("{}", name);
}
let name = String::from("sehwa");
print_name(&name); // 빌려줌, name 여전히 사용 가능
take_name(name); // 넘겨줌, 이후 name 사용 불가
대부분의 경우 &로 빌려주면 된다. "읽기만 할 건데 소유권까지 줄 필요 없지"라는 판단.
클로저 — 인라인 함수
Ruby의 블록/lambda가 Rust에서는 클로저다. 함수 인자로 넘기는 패턴이 거의 같다.
# Ruby
[1, 2, 3].map { |x| x * 2 }
[1, 2, 3].map(&method(:double)) # 메서드 참조
// Rust
vec![1, 2, 3].iter().map(|x| x * 2).collect::<Vec<_>>();
|x|가 Ruby의 { |x| }에 해당한다. 중괄호 대신 파이프(|)로 인자를 감싼다.
핵심 포인트
fn 이름(인자: 타입) -> 반환타입 { } 형태로 정의
세미콜론 없으면 반환값, 있으면 statement — 이게 핵심
early return에는 return 키워드 + 세미콜론 사용
인자는 &로 빌려주거나 소유권을 넘긴다
장점
- ✓ 함수 시그니처만 보고 입출력 타입을 알 수 있다
- ✓ Ruby와 같은 "마지막 식이 반환값" 규칙이라 적응이 빠르다
단점
- ✗ 세미콜론 실수로 컴파일 에러 나는 게 초반에 빈번하다
- ✗ 빌림/소유권 결정이 Ruby에는 없는 개념이라 함수 설계가 어렵다