소개
Functions에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 fn 키워드와 일반적인 snake case 명명 규칙을 사용하여 Rust 에서 함수를 정의하고 호출하는 방법을 배우게 됩니다.
Functions에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 fn 키워드와 일반적인 snake case 명명 규칙을 사용하여 Rust 에서 함수를 정의하고 호출하는 방법을 배우게 됩니다.
함수는 Rust 코드에서 널리 사용됩니다. 이미 언어에서 가장 중요한 함수 중 하나인 main 함수를 보셨을 것입니다. 이는 많은 프로그램의 진입점입니다. 또한 새로운 함수를 선언할 수 있는 fn 키워드도 보셨습니다.
functions라는 새 프로젝트를 만듭니다.
cargo new functions
cd functions
Rust 코드는 함수 및 변수 이름에 대한 일반적인 스타일로 snake case를 사용합니다. 여기서 모든 문자는 소문자이고 밑줄 (_) 은 단어를 구분합니다. 다음은 함수 정의의 예가 포함된 프로그램입니다.
파일 이름: src/main.rs
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
Rust 에서 함수를 정의하려면 fn을 입력한 다음 함수 이름과 괄호 세트를 입력합니다. 중괄호는 컴파일러에게 함수 본문이 시작되고 끝나는 위치를 알려줍니다.
괄호 세트를 입력한 다음 이름을 입력하여 정의한 모든 함수를 호출할 수 있습니다. another_function이 프로그램에 정의되어 있으므로 main 함수 내에서 호출할 수 있습니다. another_function을 소스 코드에서 main 함수 뒤에 정의했음을 유의하세요. 앞서 정의할 수도 있었습니다. Rust 는 함수를 어디에 정의했는지 신경 쓰지 않고, 호출자가 볼 수 있는 범위 내에서 정의되었는지 여부만 확인합니다.
함수를 더 자세히 탐구하기 위해 functions라는 새 바이너리 프로젝트를 시작해 보겠습니다. another_function 예제를 src/main.rs에 넣고 실행합니다. 다음 출력이 표시됩니다.
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/functions`
Hello, world!
Another function.
줄은 main 함수에 나타나는 순서대로 실행됩니다. 먼저 "Hello, world!" 메시지가 인쇄된 다음 another_function이 호출되고 해당 메시지가 인쇄됩니다.
함수의 시그니처 (signature) 의 일부인 특수한 변수인 *매개변수 (parameters)*를 정의할 수 있습니다. 함수에 매개변수가 있는 경우 해당 매개변수에 대한 구체적인 값을 제공할 수 있습니다. 기술적으로 구체적인 값은 *인수 (arguments)*라고 하지만, 일상적인 대화에서는 함수 정의의 변수 또는 함수를 호출할 때 전달되는 구체적인 값에 대해 매개변수와 인수라는 단어를 서로 바꿔서 사용하는 경향이 있습니다.
another_function의 이 버전에서는 매개변수를 추가합니다.
파일 이름: src/main.rs
fn main() {
another_function(5);
}
fn another_function(x: i32) {
println!("The value of x is: {x}");
}
이 프로그램을 실행해 보세요. 다음 출력을 얻을 수 있습니다.
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 1.21s
Running `target/debug/functions`
The value of x is: 5
another_function의 선언에는 x라는 매개변수가 하나 있습니다. x의 유형은 i32로 지정됩니다. another_function에 5를 전달하면 println! 매크로가 형식 문자열에서 x를 포함하는 중괄호 쌍이 있던 위치에 5를 넣습니다.
함수 시그니처에서는 각 매개변수의 유형을 반드시 선언해야 합니다. 이것은 Rust 의 설계에서 의도적인 결정입니다. 함수 정의에서 유형 주석 (type annotations) 을 요구하면 컴파일러가 코드의 다른 곳에서 어떤 유형을 의미하는지 파악하기 위해 거의 사용하지 않아도 됩니다. 또한 컴파일러는 함수가 예상하는 유형을 알고 있는 경우 더 유용한 오류 메시지를 제공할 수 있습니다.
여러 매개변수를 정의할 때는 다음과 같이 쉼표로 매개변수 선언을 구분합니다.
파일 이름: src/main.rs
fn main() {
print_labeled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {value}{unit_label}");
}
이 예제는 print_labeled_measurement라는 함수를 두 개의 매개변수로 만듭니다. 첫 번째 매개변수는 value이고 i32입니다. 두 번째는 unit_label이고 유형은 char입니다. 그런 다음 함수는 value와 unit_label을 모두 포함하는 텍스트를 인쇄합니다.
이 코드를 실행해 보겠습니다. functions 프로젝트의 src/main.rs 파일에 현재 있는 프로그램을 앞의 예제로 바꾸고 cargo run을 사용하여 실행합니다.
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/functions`
The measurement is: 5h
value의 값으로 5를, unit_label의 값으로 'h'를 사용하여 함수를 호출했으므로 프로그램 출력에는 해당 값이 포함됩니다.
함수 본문은 일련의 문 (statements) 으로 구성되며, 선택적으로 표현식 (expression) 으로 끝납니다. 지금까지 다룬 함수에는 끝나는 표현식이 포함되지 않았지만, 문 (statement) 의 일부로 표현식을 보셨습니다. Rust 는 표현식 기반 언어이므로 이를 이해하는 것이 중요합니다. 다른 언어는 동일한 구분을 갖지 않으므로 문과 표현식이 무엇인지, 그리고 그 차이가 함수 본문에 어떤 영향을 미치는지 살펴보겠습니다.
우리는 실제로 이미 문과 표현식을 사용했습니다. let 키워드를 사용하여 변수를 만들고 값을 할당하는 것은 문입니다. Listing 3-1 에서 let y = 6;은 문입니다.
파일 이름: src/main.rs
fn main() {
let y = 6;
}
Listing 3-1: 하나의 문을 포함하는 main 함수 선언
함수 정의도 문입니다. 앞의 전체 예제 자체가 문입니다.
문은 값을 반환하지 않습니다. 따라서 다음 코드에서 시도하는 것처럼 let 문을 다른 변수에 할당할 수 없습니다. 오류가 발생합니다.
파일 이름: src/main.rs
fn main() {
let x = (let y = 6);
}
이 프로그램을 실행하면 다음과 같은 오류가 발생합니다.
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found statement (`let`)
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: variable declaration using `let` is a statement
error[E0658]: `let` expressions in this position are unstable
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for
more information
let y = 6 문은 값을 반환하지 않으므로 x가 바인딩할 것이 없습니다. 이것은 C 및 Ruby 와 같은 다른 언어에서 발생하는 것과는 다릅니다. 여기서 할당은 할당 값을 반환합니다. 이러한 언어에서는 x = y = 6을 작성하여 x와 y 모두 값 6을 갖도록 할 수 있습니다. Rust 에서는 그렇지 않습니다.
표현식은 값을 평가하고 Rust 에서 작성할 나머지 코드의 대부분을 구성합니다. 5 + 6과 같은 수학 연산을 생각해 보세요. 이는 값 11로 평가되는 표현식입니다. 표현식은 문의 일부가 될 수 있습니다. Listing 3-1 에서 문 let y = 6;의 6은 값 6으로 평가되는 표현식입니다. 함수를 호출하는 것은 표현식입니다. 매크로를 호출하는 것은 표현식입니다. 중괄호로 생성된 새로운 범위 블록은 표현식입니다. 예를 들어:
파일 이름: src/main.rs
fn main() {
1 let y = {2
let x = 3;
3 x + 1
};
println!("The value of y is: {y}");
}
표현식 [2]는 이 경우 4로 평가되는 블록입니다. 해당 값은 let 문 [1]의 일부로 y에 바인딩됩니다. 지금까지 보았던 대부분의 줄과 달리 세미콜론이 없는 줄 [3]에 유의하세요. 표현식은 끝나는 세미콜론을 포함하지 않습니다. 표현식의 끝에 세미콜론을 추가하면 문으로 바뀌고 값을 반환하지 않습니다. 다음으로 함수 반환 값과 표현식을 탐구할 때 이 점을 염두에 두세요.
함수는 자신을 호출하는 코드에 값을 반환할 수 있습니다. 반환 값의 이름을 지정하지 않지만, 화살표 (->) 뒤에 해당 유형을 선언해야 합니다. Rust 에서 함수의 반환 값은 함수 본문의 블록에 있는 마지막 표현식의 값과 동일합니다. return 키워드를 사용하고 값을 지정하여 함수에서 조기에 반환할 수 있지만, 대부분의 함수는 마지막 표현식을 암시적으로 반환합니다. 다음은 값을 반환하는 함수의 예입니다.
파일 이름: src/main.rs
fn five() -> i32 {
5
}
fn main() {
let x = five();
println!("The value of x is: {x}");
}
five 함수에는 함수 호출, 매크로 또는 let 문조차 없습니다. 단지 숫자 5만 있습니다. 이것은 Rust 에서 완벽하게 유효한 함수입니다. 함수의 반환 유형도 -> i32로 지정되어 있습니다. 이 코드를 실행해 보세요. 출력은 다음과 같아야 합니다.
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/functions`
The value of x is: 5
five의 5는 함수의 반환 값이며, 반환 유형이 i32인 이유입니다. 이를 자세히 살펴보겠습니다. 두 가지 중요한 부분이 있습니다. 먼저, let x = five(); 줄은 함수의 반환 값을 사용하여 변수를 초기화하고 있음을 보여줍니다. five 함수가 5를 반환하므로 해당 줄은 다음과 같습니다.
let x = 5;
둘째, five 함수에는 매개변수가 없고 반환 값의 유형을 정의하지만, 함수 본문은 세미콜론이 없는 외로운 5입니다. 왜냐하면 반환하려는 값인 표현식이기 때문입니다.
다른 예를 살펴보겠습니다.
파일 이름: src/main.rs
fn main() {
let x = plus_one(5);
println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
x + 1
}
이 코드를 실행하면 The value of x is: 6이 출력됩니다. 그러나 x + 1을 포함하는 줄의 끝에 세미콜론을 배치하여 표현식에서 문으로 변경하면 오류가 발생합니다.
파일 이름: src/main.rs
fn main() {
let x = plus_one(5);
println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
x + 1;
}
이 코드를 컴파일하면 다음과 같은 오류가 발생합니다.
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
error[E0308]: mismatched types
--> src/main.rs:7:24
|
7 | fn plus_one(x: i32) -> i32 {
| -------- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
8 | x + 1;
| - help: remove this semicolon
주요 오류 메시지인 mismatched types는 이 코드의 핵심 문제를 드러냅니다. plus_one 함수의 정의는 i32를 반환한다고 말하지만, 문은 값으로 평가되지 않으며, 이는 유닛 타입인 ()로 표현됩니다. 따라서 아무것도 반환되지 않아 함수 정의와 모순되어 오류가 발생합니다. 이 출력에서 Rust 는 이 문제를 해결하는 데 도움이 되는 메시지를 제공합니다. 세미콜론을 제거하라고 제안하며, 그러면 오류가 수정됩니다.
축하합니다! 함수 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.