Funtion, Method and Associated Functions

Function은 특정 구조체나 인스턴스와 관계없이 독립적으로 정의되는데, Method는 구조체(또는 enum) 인스턴스의 상태를 읽거나 변경하는 동작을 구현하며, Assosiated Functions는 특정 타입과 연결되어 있지만 해당 타입의 인스턴스와는 독립적인 함수를 의미합니다.

1. 함수와 메소드의 차이점

가. 함수 (Function)

  • 특정 구조체나 인스턴스와 관계없이 독립적으로 정의됩니다.
  • fn 키워드를 사용해 선언하며, 어디서든 호출할 수 있습니다.
  • 호출 시 add(3, 5)처럼 함수 이름만 사용합니다.
fn add(a: i32, b: i32) -> i32 {
    a + b
}

나. 메소드 (Method)

  • 구조체(struct), 열거형(enum) 등 특정 타입의 impl 블록 안에서 정의됩니다.
  • 첫 번째 파라미터로 반드시 self, &self, &mut self 중 하나를 받습니다.
  • 인스턴스를 통해서만 호출할 수 있으며, rect.area()처럼 점(.) 연산자를 사용합니다.
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

이처럼 Rust에서는 함수는 독립성, 메소드는 인스턴스와의 종속성이 가장 큰 차이입니다.

2. 함수와 메소드의 차이점

가. 함수(Function)

(1) 장점

  • 독립적이어서 다양한 곳에서 재사용하기 쉽고, 유닛 테스트가 간편합니다.
  • 간단한 연산이나 유틸리티 작업에 적합합니다.
  • 이름만으로 호출하므로 사용이 직관적입니다.

(2)

  • 구조체 등 데이터 타입과 직접적으로 연관된 행동을 묶어 표현하기 어렵습니다.
  • 많은 함수가 생기면 네임스페이스 충돌이나 코드의 구조적 복잡도가 증가할 수 있습니다.
  • 데이터 캡슐화와 추상화에서 약점이 있습니다.

나. 메소드(Method)

(1)장점

  • 데이터와 행위의 결합(캡슐화): 구조체의 로직과 동작을 impl 블록에 모아, 객체지향적 코드를 짤 수 있습니다.
  • 가독성: 인스턴스.메소드(…) 형태로 표현해 자연스럽고 읽기 쉬운 코드를 만듭니다.
  • 확장성: 타입별로만 동작을 추가하거나 오버라이드하는 데 용이합니다.

(2) 단점

  • 인스턴스가 있어야만 호출 가능합니다. 즉, 독립적으로 동작할 수 없는 경우가 많습니다.
  • 객체의 상태 변경이 필요하다면 &mut self 등으로 가변 참조를 써야 하므로, 소유권·빌림 규칙에 주의를 기울여야 합니다.
  • 단순 기능(예: 수학 연산 등)에는 불필요하게 복잡합니다.
구분장점단점
함수독립성, 재사용성, 간결함, 테스트 용이성데이터 캡슐화 불가, 네임스페이스 충돌 위험
메소드데이터와 로직 결합, 캡슐화, 가독성, 타입별 동작 확장 용이인스턴스 필요, 소유권·참조 규칙 신경써야, 불필요한 복잡성 가능

Rust에서는 함수는 독립적이고 범용적인 동작에, 메소드는 인스턴스와 연관된 행동에 사용하는 것이 일반적입니다.

2. 메소드와 연관 함수의 차이점

가. 메소드

  • 메서드는 반드시 첫 번째 인자로 self, &self 또는 &mut self를 받습니다.
  • 구조체 인스턴스를 통해 점(.) 연산자로 호출합니다.
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };  

    // 메소드 호출
    println!("사각형의 면적: {}", rect.area());
}

나. 연관 함수

  • 연관 함수는 self를 인자로 받지 않습니다.
  • 타입 이름을 통해 이중 콜론(::) 연산자로 호출합니다.
  • 주로 생성자 역할(예: new)로 사용되고, 구조체 등의 유틸리티 함수로도 사용됩니다.

(예제 1 – 생성자)

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    // 연관 함수 사용
    let rect = Rectangle::new(30, 50);
    println!("사각형의 면적: {}", rect.area());    
}

fn new의 반환 타입이 Rectange 구조체입니다.

(예제 2 – 유틸리티 함수)

struct Circle {
    radius: f64,
}

impl Circle {
    // 연관 함수 (유틸리티 함수)
    fn area(radius: f64) -> f64 {
        std::f64::consts::PI * radius * radius
    }
}

fn main() {
    // 연관 함수 호출 (유틸리티 함수)
    let circle_area = Circle::area(5.0);
    println!("Circle area: {}", circle_area);
}

메소드와 달리 Circle::area(5.0) 처럼 ::을 붙여 호출합니다.

위 코드를 실행하면
Circle area: 78.53981633974483라고 처리 결과가 제대로 표시되지만

“구조체 Circle안의 radius 필드가 사용되지 않았다”는 경고가 표시됩니다.


다시 말해 연관 함수의 인수 radius는 이름은 같지만 구조체의 radius가 아닙니다. 또한 Circle이란 인스턴스를 생성하지 않고도 원의 면적을 구했습니다.

3. 메소드와 연관 함수의 장단점

가. 메서드

(1) 사용 목적

  • 구조체(혹은 enum) 인스턴스의 **상태(필드)**를 읽거나 변경하는 동작을 구현합니다.
  • 객체의 행동을 정의하고, 객체의 데이터와 직접 상호작용합니다.

(2) 장점

  • 캡슐화: 인스턴스의 데이터를 안전하게 다루며, 객체의 상태를 직접 관리할 수 있습니다.
  • 가독성:rect.area()처럼 객체의 행동을 자연스럽게 표현할 수 있습니다.
  • 유지보수성: 객체의 동작이 구조체 내부에 모여 있어 코드 관리가 용이합니다.

나. 연관 함수

(1) 사용 목적

  • 구조체의 생성자 역할(예: new)이나, 인스턴스와 무관한 동작(예: 유틸리티 함수)을 구현합니다.
  • 인스턴스 없이 타입 자체에 대해 동작하는 기능을 제공합니다.

(2) 장점

  • 유연성: 인스턴스가 없어도 타입 이름으로 직접 호출할 수 있습니다(Rectangle::new() 등).
  • 명확성: 생성자나 특정 타입 관련 기능을 명확하게 분리할 수 있습니다.
  • 재사용성: 인스턴스와 무관한 기능을 여러 곳에서 활용할 수 있습니다.

다. 요약

  • 메서드는 객체의 상태와 밀접하게 연관된 동작을 구현하며, 객체지향적 설계와 데이터 캡슐화에 강점을 가집니다.
  • 연관 함수는 객체 생성이나 타입 자체와 관련된 기능을 제공하며, 인스턴스가 필요 없는 동작을 분리해 코드의 명확성과 재사용성을 높입니다.

러스트에서 클로저(Closure)의 개념

러스트의 클로저(Closure)함수형 프로그래밍(Functional Programming)의 핵심 개념 중 하나로서, 변수에 저장하거나 다른 함수에 인자로 넘길 수 있는 익명 함수이며, 정의된 위치의 외부 변수를 자동으로 사용할 수 있습니다. 이것이 일반 함수와 큰 차이점입니다.

함수형 프로그래밍은 다음과 같은 특성을 중시합니다:

특징설명
일급 함수 (First-class function)함수를 값처럼 변수에 할당하거나 인자로 전달 가능
고차 함수 (Higher-order function)함수를 인자로 받거나 함수를 반환하는 함수
불변성 (Immutability)상태를 변경하지 않고 새로운 값을 생성
순수 함수 (Pure Function)동일한 입력에 대해 항상 동일한 출력을 반환, 부작용 없음

👉 클로저는 이러한 특징, 특히 일급 함수고차 함수 개념을 구현하는 데 매우 적합합니다.


1. 클로저(Closure) 란?

클로저는 익명 함수로 fn과 함수이름이 없습니다.
변수에 저장하거나 다른 함수에 인자로 넘길 수 있습니다.

fn main() {
    let add = |a, b| a + b;
    println!("{}", add(2, 3)); // 5
}
  • |a, b| a + b는 익명 함수(anonymous function) 형태이며, 입력값 a와 b를 받아서 a + b를 반환합니다.
  • 이 클로저는 변수 add에 저장됩니다. 다시 말해 add는 클로저를 가리키는 변수(바인딩된 이름)입니다.
  • 따라서, 이후에는 add(2, 3)처럼 add를 통해 클로저를 호출할 수 있습니다.
  • println!(“{}”, add(2, 3));은 add(2, 3)을 호출하여 2 + 3을 계산하고 결과는 5입니다. 이 결과를 println!을 사용해 출력합니다.

가. function으로 만들었다면?

아래와 같은 코드가 됩니다. 함수는 입력 값과 반환 형식의 타입을 반드시 명시해야 하는데, 클로저는 타입이 추론되는 점도 다릅니다.

fn add(a:i32,b:i32) -> i32 {
    a + b
} 

fn main() {    
    println!("{}", add(2, 3));
}

2. 클로저의 환경 캡처(Capturing the Environment)

클로저는 정의된 위치의 외부 변수를 자동으로 사용할 수 있습니다. 이게 일반 함수와 큰 차이점입니다.

fn main() {
    let x = 5;

    let add_x = |a| a + x;

    println!("{}", add_x(3)); // 8}
  • 여기서 x는 클로저 외부에서 정의된 변수지만, 클로저 내부에서 자동으로 캡처(capture) 됩니다.
  • 함수에서는 이런 일이 불가능합니다. 다시 말해 x가 함수의 입력 인수로 지정돼야 합니다.
    fn add_x(a: i32, x: i32) -> i32 {
    a + x
    }
  • 또한 클로저의 인수로 a만 있기때문에 add_x의 입력 값으로 a만 입력하면 되고, x는 입력할 필요가 없습니다.
  • 실행 결과는 3 + 5 = 8입니다.

3. 캡처 방식에 따른 트레잇

Rust는 클로저가 외부 변수를 어떻게 사용하는지에 따라 다음 3가지 트레잇 중 하나로 자동 구현됩니다:

트레잇설명예시
Fn불변 참조로캡처 (&T)읽기만 하는 클로저
FnMut가변 참조로 캡처 (&mut T)외부 값을 수정하는 클로저
FnOnce값을 소유하여 캡처 (T)한 번만 호출 가능한 클로저

가. Fn (읽기만 함)

fn main() {
    let x = 1;
    let closure = |a| a + x;
    println!("{}", closure(2)); // 3
}
  • x를 읽기만 하므로 Fn 트레잇으로 작동합니다.
  • 실행 결과는 2 + 1 = 3입니다.

나. FnMut (수정)

fn main() {
    let mut x = 1;
    let mut closure = |a| {
        x += a;
        x
    };

    println!("{}", closure(2)); // 3
    println!("{}", closure(2)); // 5
}
  • x += a처럼 외부 값인 x를 수정하므로 FnMut 트레잇이 필요합니다. 내부적으로 사용되고 FnMut는 어디에도 없습니다.
  • 한번 실행하면 x가 2 + 1 = 3이 되고, 두번째 closure(2)는 2 + 3 = 5를 반환합니다.
  • 당연히 x 변수는 mut(가변 변수)로 선언되었습니다.

다. FnOnce (이동)

fn main() {
    let s = String::from("hello");

    let closure = move || {
        println!("{}", s);
    };

    closure();
    // closure(); // ❌ 두 번 호출하면 에러: FnOnce
}
  • let closure = move || {
    println!(“{}”, s);
    };
    이 줄에서 클로저를 정의하고 closure라는 변수에 저장합니다.
  • move 키워드는 클로저가 외부 변수 s의 소유권을 가져오게 만듭니다.
  • 즉, s는 더 이상 main 함수 안에서는 사용할 수 없습니다.
  • 클로저 내부에서 s를 사용하므로 소유권이 클로저로 이동(move) 됩니다.
  • 이 클로저는 FnOnce 트레잇만 구현됩니다.
    이유: s의 소유권을 가져오면, 클로저는 단 한 번만 호출할 수 있기 때문입니다.
  • s가 외부 변수이므로 ||안에 넣지 않고, 실행문 안에 들어가 있습니다.

4. 클로저 vs 함수 비교 표

항목클로저 (Closure)함수 (Function)
정의 방식`let c =x
이름익명 함수(Anonymous) 형태, 변수에 저장명시적으로 이름을 정의
타입 명시 여부생략 가능 (`x
리턴 타입대부분 추론됨 (→ 생략 가능)명시하거나 생략 가능 (하지만 복잡한 경우 명시 권장)
외부 변수 접근가능 (환경 캡처: by ref, mut ref, move)불가능 (오직 인자로만 데이터 전달)
사용 가능 위치함수 내에서 정의, 변수처럼 전달모듈 내 어디든 정의 가능
함수 포인터와의 호환fn 타입으로 직접 전달하려면 명시 필요기본적으로 fn 타입 (e.g. fn(i32) -> i32)
트레잇 구현Fn, FnMut, FnOnce 자동 구현됨일반 함수는 Fn 트레잇으로 처리 가능
호출 방식변수명으로 호출 (c(3))함수명으로 호출 (c(3))
Move 가능 여부move 키워드로 명시적 이동 가능소유권 개념 없음 (독립된 스코프)
유연성매개변수와 환경 캡처를 자유롭게 조합환경 변수와는 독립적
실행 성능최적화 후 함수 수준의 성능 가능고정된 바이너리 코드로 일반적으로 더 최적화
사용 예일시적인 로직, 고차 함수 인자, iterator재사용 가능한 명령 블록, API 정의
중괄호 생략 가능 여부

단일 표현식이면 생략 가능생략 불가
항상 중괄호 {} 필요

5. 요약 정리

항목설명
클로저외부 환경 캡처 가능한 익명 함수
Fn, FnMut, FnOnce클로저의 호출 방식에 따른 트레잇 분류

함수, if와 match 표현식

함수는 코드의 재사용과 구조화를 위한 기본 단위로서 매개변수와 반환값이 있을 수 있습니다. 또한 if와 match는 중요한 제어 흐름 도구로서, let과 결합하여 변수에 값을 대입하는 표현식도 됩니다. match의 경우 모든 경우를 망라하기 위해 _를 사용하는 것이 특이합니다.


🔧 함수 정의

fn main() {
greet("Rust");
}

fn greet(name: &str) {
println!("Hello, {}!", name);
}
  • fn 키워드로 함수를 정의합니다.
  • 함수는 매개변수와 반환 타입을 명시할 수 있습니다. 그러나, 매개변수나 반환 값이 있다면 반드시 형식(타입)을 지정해야 합니다. 위에서 main 함수에는 매개변수가 없고, greet에는 매개변수 name이 있으므로 형식을 &str로 지정했습니다.
  • &str은 문자열 슬라이스(문자열 참조)입니다.
  • main함수에서 greet 함수를 호출하고, greet 함수의 name 매개변수로 Rust를 전달하고 있으므로, 위 코드를 실행하면 아래 화면과 같이 Hello, Rust!라고 화면에 표시됩니다.
Run을 실행한 결과 Hello, Rust!가 화면에 출력된 화면입니다.

위 화면은 D:\rust-practice 폴더에서 cargo new day3를 실행한 다음 위 코드로 대체하고 실행한 화면입니다.

name 다음의 형식을 제거하고 실행(Run) 하면 아래와 같이 복잡한 에러 메시지가 표시되는데, name에 대한 형식을 지정하라는 의미입니다.

name 다음에 형식 지정이 없어서 지정하라는 에러 화면입니다.

위 화면에서 name 다음에 :을 입력하면 &str이 제시되므로 tab키를 눌러 제안을 수용하면 쉽게 코드를 완성할 수 있습니다.


🔁 반환값이 있는 함수

fn add(a: i32, b: i32) -> i32 {
a + b // 세미콜론 없음 → 반환값
}
  • 함수의 마지막 표현식(Expression)이 반환값입니다. 여기서는 a + b 입니다.
  • -> 다음의 i32가 반환 값의 형식을 지정하는 것입니다.
  • 세미콜론(;)이 붙으면 실행문(Statement)으로 값이 반환되지 않습니다.
  • return키워드를 사용할 수도 있지만, 마지막 줄에 return 없이 값을 놓는 것이 일반적입니다.
fn add(a: i32, b: i32) -> i32{
    return a + b
}
  • 위 함수는 출력문이 없으므로 화면에 어떠한 값도 출력하지 않습니다.
    값을 출력하려면 println! 매크로를 사용해야 합니다.
fn main() {
    let sum = add(5, 10);
    println!("5와 10의 합은: {sum}"); // 15
}

fn add(a: i32, b: i32) -> i32{
    a + b
}

위 코드는 main함수에서 add 함수에 5와 10을 전달하고 a + b의 값을 반환받아 값 15를 sum 변수에 대입한 후 println!를 이용해 “5와 10의 합은: 15″라고 화면에 출력하는 것입니다.


🔸 if 표현식

Rust에서 if는 표현식이며, 값으로 사용할 수 있습니다. 다시 말해 let 예약어를 이용해 변수에 if 표현식으로 결정되는 값을 변수에 대입할 수 있습니다.

fn main() {
let score = 85;
let grade = if score >= 90 {
"A"
} else if score >= 80 {
"B"
} else {
"C"
};
println!("성적: {grade}");
}
  • if는 블록의 결과를 반환합니다.
  • 각 분기의 결과는 같은 타입이어야 합니다.
  • 위 코드를 실행하면 score가 90보다 작고, 80보다 크므로 “성적: B”가 화면에 출력됩니다.

🔶 match 표현식

match는 패턴 매칭을 제공하는 강력한 제어문입니다.

fn main() {
let number = 3;

match number {
1 => println!("하나"),
2 => println!("둘"),
3 => println!("셋"),
_ => println!("기타"),
}
}
  • _는 위에 해당하지 않는 모든 경우를 의미하는 와일드카드입니다. ‘아무거나(any value)’라고 이해하면 편합니다.
  • 각 분기(arm)에는 =>로 실행 코드를 지정합니다(The => operator that separates the pattern and the code to run).
  • println!를 사용했는데도 ;을 붙이지 않는 점을 주의해야 합니다.
  • 위 코드를 실행하면 “셋”이라고 화면에 표시됩니다.
  • match는 반드시 모든 경우를 처리해야 합니다.
    다시 말해 _가 없으면 “i32 형식에 해당하는 수 중 1,2,3만 처리해서 i32의 최소값부터 0까지와 4부터 i32의 최대값은 커버하지 못했다”고 하는 non-exaustive patterns(총망라 하지 않은 패턴) 에러가 표시됩니다.

또한 아래와 같이 _를 맨 위에 놓으면 ‘모든 경우’가 되므로, number의 값이 1이거나 2 또는 3이더라도 “기타”를 출력하게 됩니다. 1,2,3이 아닌 4인 경우 “기타”를 출력하는 것은 너무나 당연합니다.

fn main() {
    let number = 3;

    match number {
        _ => println!("기타"),
        1 => println!("하나"),
        2 => println!("둘"),
        3 => println!("셋"),        
    }
}

📌 match를 값으로 사용하기

fn main() {
let day = 3;
let weekday = match day {
1 => "월요일",
2 => "화요일",
3 => "수요일",
_ => "기타",
};
println!("요일: {}", weekday);
}
  • match는 if와 마찬가지로 표현식이므로 변수에 바로 match 표현식의 결과 값을 할당할 수 있습니다.
  • 이전 예에서는 => 다음에 println!를 사용했는데, 여기서는 “화요일” 등의 반환값을 지정한 점이 다릅니다.
  • 위 코드를 실행하면 “요일: 수요일”이 출력됩니다.


🧠 요약

  • 함수는 fn으로 정의하며, 매개변수와 반환 타입 지정 가능
  • if와 match는 모두 표현식으로, 값을 반환할 수 있음
  • match는 매우 강력한 패턴 매칭 도구이며, 모든 경우를 반드시 다뤄야 함