반응형

간단하게 정리해본 Rust 강좌입니다. 

 

다음 문서를 기반으로 작성했습니다.

 

Tour of Rust

https://tourofrust.com/00_ko.html 



2022. 09. 07  최초 작성

2022. 12. 19  최종 작성



Hello World

다음 코드는 문자열 "Hello, 🦀"를 화면에 출력해주는 Rust 프로그램의 가장 간단한 형태입니다.

fn main() {
    println!("Hello, 🦀");
}

 

첫줄에서 이름이 main인 함수를 정의하기 위해 fn 키워드 다음에 함수 이름 main을 적습니다. main 함수는 프로그램이 실행되면 가장  먼저 실행되는 함수입니다.  main 뒤에 오는 괄호 안에 아무것도 없는 것은 함수에서 전달받는 값이 아무것도 없다는 의미입니다.

fn main() {
    println!("Hello, 🦀");
}

 

중괄호 {}안에 main 함수가 호출되면 실행할 코드를 추가합니다.  여기에서는 println 함수를 실행합니다.

fn main() {

    println!("Hello, 🦀");

}

 

println!는 큰따옴표(“) 안에 있는 문자열을 화면에 출력해줍니다. 또한 Rust에서 모든 문의 끝에는 세미콜론(;)이 있어야 합니다.

println!("Hello, 🦀");

 

코드를 실행해보면 화면에 "Hello, 🦀”가 출력됩니다. 

 

실행결과

Hello, 🦀



변수

변수를 선언하기 위해 let 키워드를 적고 뒤에 변수 이름을 적습니다. = 다음에 변수에 저장할 값을 적어주면 됩니다. 또는 변수 x에 정수 42를 대입한다고 합니다.  변수에 대입된 값에 대한 데이터 타입을 지정해주지 않아도 Rust가 알아서 데이터 타입을 추측합니다.  정수의 경우 디폴트 데이터 타입은 32비트 부호 있는 정수입니다. 

let x = 42;

 

변수 x에 정수 42를 대입하면 코드에서 정수 42대신에 변수 x를 사용할 수 있습니다.

 

다음처럼 직접 정수 42를 사용하는 대신에 

fn main() {
    println!("{}", 42 + 1);
}

실행결과

43

 

변수 x에 42를 대입한후 정수 42가 위치하던 자리에 변수 x를 사용할 수 있습니다.

fn main() {
    let x = 42;
    println!("{}", x + 1);
}

실행결과

43

 

변수 이름은  snake_case 형태로 짓습니다. 변수 이름을 구성하는 단어를 소문자로 적고 단어가 이어질때 마다 언더바(_)를 사용하는 방법입니다. 

let apple_price = 100;

 

다음 코드처럼 변수를 선언하면서 변수에 값을 바로 대입하거나

let x = 42; // 변수 x를 선언하면서 바로 변수 x에 정수 42를 대입합니다.

 

또는 다음 코드처럼 변수를 먼저 선언하고 나서 나중에 변수에 값을 대입할 수도 있습니다.  

let x; // 변수 x를 선언합니다.
x = 42; // 변수 x에 정수 42를 대입합니다.

 

변수를 선언 한 후, 나중에 변수에 값을 대입하는 경우  컴파일러는 변수에 값이 대입되기 전에는 변수를 사용하지 못하도록 합니다.

그래서 다음 코드에서 볼 수 있듯이 변수 x에 값을 대입하기 전에 변수 x의 값을 출력하기 위해 접근하려고 하면  에러가 발생합니다. 

fn main() {
    let x;


    println!("{}", x); // 초기화되지 않은 변수 x에 접근했기 때문에 에러가 발생합니다.
}

 

실행하면 다음 에러가 발생합니다. 여기에서는 변수의 데이터 타입을 선언 안했다고 에러를 보여주고 있습니다. 예상했던 에러 메시지가 아닙니다. 

error[E0282]: type annotations needed

 

에러를 해결하기 위해 다음처럼 변수에 데이터 타입을 지정해주면 예상했던 에러 메시지를 볼 수 있습니다. 

fn main() {
    let x : i32;

    println!("{}", x); // 초기화되지 않은 변수 x에 접근했기 때문에 에러가 발생합니다.
}

 

변수 x를 초기화하지 않고 사용했다는 에러 메시지를 보여줍니다.

 

실행결과

error[E0381]: used binding `x` isn't initialized



이 문제를 해결하려면 다음처럼 변수 x에 값을 대입해줘야 합니다. Rust에서는 변수에 값이 대입되야 변수에 접근이 가능합니다. 

fn main() {
    let x;


    x = 42;
    println!("{}", x); // 변수 x에 값이 대입되었기 때문에 사용할 수 있습니다.
}

 

실행결과

42



변수를 선언한 후 코드에서 해당 변수를 사용한 적이 없으면 다음처럼 컴파일시 해당 변수를 사용하지 않았다는 경고문이 보입니다. 

fn main() {
    let x = 32;
}

 

실행결과

warning: unused variable: `x`

 

변수 이름 앞에 밑줄( _ )를 추가해주면 컴파일시 변수를 사용하지 않았다는 경고문을 안보이게 해줍니다. 

fn main() {
    let _x = 32;
}



변수 선언시 데이터 타입을 명시적으로 지정하기 위해 콜론( : )을 사용합니다.

부호 있는 정수 데이터 타입으로는 i8, i16, i32, i64, i128이 있고, 부호 없는 정수 데이터 타입으로는 u8, u16, u32, u64, u128이 있습니다. 

let x: i32; // 변수 x의 타입을 부호 있는 32비트 정수(signed 32-bit integer)로 지정합니다.
x = 42;  // 변수 x에 정수 42를 대입합니다.

 

데이터 타입을 지정하여 변수를 선언한 경우에도 변수를 선언하면서 값을 바로 대입할 수 있습니다. 

let x: i32 = 42;



동일한 이름의 변수를 여러 번 다시 선언하는 것이 가능합니다.  이때 데이터 타입도 변경이 가능합니다. 

fn main() {

    let x = 10;    // 변수 x를 32비트 정수 타입으로 선언합니다.
    let x = x + 3; // 앞에서 선언한 변수 x에 3을 더한 값을 저장하는 새로운 변수 x를 선언합니다.
                  // 이제 앞에서 선언한 변수 x는 지금 선언한 변수 x로 대체됩니다.

    println!("{}", x);


    let x = "hello"; // 정수 타입의 값을 저장했던 변수 x를 문자열 타입으로 변경하여 다시 선언할 수 있습니다.
    println!("{}", x);
}

 

실행결과

13

hello



밑줄( underscore, _ )은 대입된 값을 버리는 용도로 사용됩니다.

//대입된 값이 저장되지 않고 버려집니다.
let _ = 42;

// get_thing 함수를 호출하여 얻은 결과를 저장하지 않고 버립니다.
let _ = get_thing();



한 쌍의 대괄호는 블록을 선언합니다. 블록 내에서 선언한 변수는 지역변수가 되며 블록을 벗어나는 순간 해당 변수를 사용할 수 없습니다. 

fn main() {

    // 변수 x에 문자열 "out"을 저장합니다.
    let x = "out";


    {// 여기에서 새로운 블록이 시작됩니다. 블록바깥에 있는 변수 x를 여기서 사용가능하지만

     // 블록 내부에서 같은 이름의 변수 x를 선언하면 블록내에서는 x가 가리키는 것이 블록 내의 변수 x를 의미하게 됩니다.

        // 변수 x에 문자열 "in"을 저장합니다.
        // 블록 바깥에서 선언한 변수 x와는 다른 변수가 됩니다.
        let x = "in";

        // "in"이 출력됩니다.
        println!("{}", x);


        // 블록내에서 선언된 변수 x는 블록을 벗어나면 스택에서 삭제됩니다. 

    } // 여기에서 블록이 끝납니다.

    // 블록 바깥에 있던 변수 x에 저장된 값인 "out"이 출력됩니다.
    println!("{}", x);
}

 

실행해보면 블록 바깥과 블록 안에서 출력된 변수 x의 값이 다른 것을 볼 수 있습니다.

 

실행결과

in      블록 내부에서 출력된 값

out    블록 바깥에서 출력된 값



변수의 값 변경하기

Rust에는 두 가지 종류의 변수가 있습니다.  

 

1. let을 사용하여 변수를 선언하면 변수에 대입된 값을 변경할 수 없는 (immutable) 변수가 됩니다. 

let x = 42; // let을 사용하여 정수 42를 저장하는 변수 x를 선언합니다.
x = 100; // 에러가 발생합니다.

 

2. let mut를 사용하여 변수를 선언하면 변수에 대입된 값을 변경할 수 있는(mutable) 변수가 됩니다.

let mut x = 42; // let mut를 사용하여 정수 42를 저장하는 변수 x를 선언합니다.
x = 100; // 이제 변수 x에는 정수 100이 저장되어 있습니다.



변수에 저장된 값을 다시 변경하려면 변수 선언시 let 키워드 대신에 let mut 키워드를 사용해야 합니다.

fn main() {

    // 변수 선언시 let mut 키워드를 사용해야 변수의 값을 변경할 수 있습니다.
    let mut x = 42;

    println!("{}", x);
   

    // 변수의 값을 변경합니다.
    x = 13;
   
    println!("{}", x);
}

 

실행 결과 값이 변경된 것을 볼 수 있습니다.

 

실행결과

42

13



데이터 타입

Rust에서 제공하는 데이터 타입입니다. 

 

bool

부울값 true 또는 false 값을 가집니다.

 

u8 u16 u32 u64 u128

부호가 없는 정수인 양의 정수를 저장할 수 있습니다.

u 뒤에 있는 숫자는 저장가능한 최대 비트수를 의미합니다.

 

 i8 i16 i32 i64 i128 

부호가 있는 정수인 양의 정수 또는 음의 정수를 저장할 수 있습니다. 

i 뒤에 있는 숫자는 저장가능한 최대 비트수를 의미합니다.

 

usize isize

포인터 크기를 의미하는 정수를 저장합니다. 에를 들어 메모리에 저장되어 있는 값들에 대한 인덱스와 크기입니다.

 

f32 f64

실수를 저장할 수 있습니다.

f 뒤에 있는 숫자는 저장가능한 최대 비트수를 의미합니다.

 

튜플(tuple)

여러 종류의 데이터 타입을 같이 저장할 수 있는 컬렉션(collection)입니다.

 

배열(array)

튜플과 달리 같은 종류의 데이터 타입의 값을 저장할 수 있는 컬렉션입니다.

 

char

하나의 문자를 저장하며 문자를 작은 따옴표(')로 감싸야합니다. 

 

str

문자들로 구성되는 문자열을 저장하며 문자열을 큰 따옴표(")로 감싸야 합니다. 



숫자 데이터 타입은 숫자 뒤에 자료형 이름을 붙여 명시적으로 데이터 타입을 지정할 수 있습니다 

fn main() {

    let a = 12; // 데이터 타입을 지정하지 않으면 정수는 i32,부호있는 32비트정수입니다.
    let b = 12u8; // 부호없는 8비트정수를 대입합니다.

    let c = 4.3; // 데이터 타입을 지정하지 않으면 실수는 f64, 64비트 실수입니다.
    let d = 4.3f32; // 32비트 실수를 대입합니다.
}



튜플

Rust에도  튜플(tuple)이  있습니다. 튜플은 괄호 안에 값을 콤마로 구분하여 추가합니다. 튜플에 저장되는 값이 모두 똑같은 데이터 타입이 아니어도 됩니다. 

fn main() {

    let pair = ('a', 17); //문자와 정수를 하나의 튜플에 같이 저장할 수 있습니다.
    

    //튜플의 값을 나누어 출력합니다.
    println!("{}", pair.0);
    println!("{}", pair.1);
}

 

실행결과

a

17



튜플에 저장되는 값에 대한 데이터 타입을 다음처럼 각각 지정할 수 있습니다. 

fn main() {

    let pair: (char, i32) = ('a', 17);
   
    println!("{}", pair.0);
    println!("{}", pair.1);
}

 

실행결과

a

17



튜플 형태로 변수를 선언하여 값을 대입할 수 있습니다. 

fn main() {

    let (some_char, some_int) = ('a', 17);
   
    println!("{}", some_char);
    println!("{}", some_int);
}

 

실행결과

a

17



함수가 튜플을 반환할 수 있습니다. 

fn main() {

    let str = "1234567890";
    

    // 인덱스 4를 기준으로 문자열을 둘로 나누어 튜플에 저장합니다. 
    // left에는 인덱스 0 ≦ s < 3 범위의 문자가 저장되고
    // right에는 인덱스 4 ≦ s 범위의 문자가 저장됩니다.
    let (left, right) = str.split_at(4);

    println!("{}", left);
    println!("{}", right);
}

 

실행결과

1234

567890



_를 사용하여 튜플의 일부값을 저장하지 않도록 할 수 있습니다. 

fn main() {

    let str = "1234567890";
    

    // 바로 전 예제 코드에서 left에 해당하는 값을 저장하지 않습니다.
    let (_, right) = str.split_at(4);

    println!("{}", right);
}

 

실행결과

567890



데이터 타입  변환

as 키워드를 사용해 데이터 타입을 변환할 수 있습니다. 

 

다음 코드는 부호 없는 8비트 정수로 선언된 변수 a를 부호없는 32비트 정수로 선언된 변수 b와 더하기 위해서 변수 a를 as 키워드를 사용하여 부호없는 32비트 정수 타입으로 변환했습니다.

fn main() {
    let a = 13u8;
    let b = 7u32;

    let c = a as u32 + b;

    println!("{}", c);

}

 

as 키워드를 제거하면 다음과 같은 에러가 납니다. 

fn main() {
    let a = 13u8;
    let b = 7u32;

    let c = a  + b;

    println!("{}", c);

}

 

데이터 타입이 다른 정수끼리 연산이 안된다는 에러입니다. 

error[E0308]: mismatched types



상수

여러 번 사용되는 고정된 값을 상수로 지정하여 사용합니다. 

사용될 때 값이 복사되는 변수와 달리, 상수는 컴파일 타임에 상수 적었던 부분을 직접 값으로 대체합니다.

변수와 달리, 상수는 반드시 명시적으로 자료형을 지정해야 합니다.

 

상수의 이름은 전부 대문자로 적고 언더바(_)로 단어 사이를 구분하는 SCREAMING_SNAKE_CASE 형태를 갖습니다.  

const PI: f64 = 3.14159;

fn main() {

    let r : f64 = 10.0;
    let circumference = 2.0 * PI * r;

    println!("{}", circumference);
}

 

실행결과

62.8318



배열

배열(array)은 같은 데이터 타입을 가지는 값을 원소로 가지고 있는 컬렉션입니다. 

배열의 데이터 타입은  [T;N] 형태로 주어지며, 여기서 T는 원소의 데이터 타입, N은 원소의 개수입니다. 

 

배열에 포함되어 있는 원소는 인덱스를 사용하여 가져 올 수 있습니다. 인덱스는 0부터 시작합니다. 

fn main() {

    // 부호없는 32비트 정수 i32 데이터 타입의 정수 3개를 원소로 가지는 배열 nums를 선언합니다.
    let nums: [i32; 3] = [1, 2, 3];

    // 배열의 원소를 모두 출력합니다.
    println!("{:?}", nums);

    // 두번째 원소(인덱스 1)를 출력합니다. 인덱스는 0부터 시작하므로 두번째 원소의 인덱스가 1이 됩니다.
    println!("{}", nums[1]);
}

 

실행결과

[1, 2, 3]

2



함수

fn 키워드를 사용하여 함수를 선언합니다.

함수의 이름은 소문자로 적고 단어 사이를 언더바(_)로 연결하는 snake_case 형태를 사용합니다. 

 

함수를 호출시 아규먼트를 통해 데이터를 함수로 넘겨줄 수 있습니다.

함수는 아규먼트 없이 호출될 수도 있고 한 개 이상의 아규먼트와 함께 호출될 수도 있습니다. 

 

다음 예제 코드는 main 함수에서 greet 함수를 호출합니다.  greet 함수에는 전달받는 아규먼트가 없습니다.

greet 함수에서 main 함수로 값을 전달하는 것도 없습니다.  

fn greet() {
    println!("Hi there!");
}

fn main() {
    greet()
}

 

실행결과

Hi there!



다음 코드는 fair_dice_roll 함수에서 자신을 호출한 main 함수로 32비트 부호 있는 정수(i32)를 전달합니다. 화살표 다음에 함수에서 자신을 호출한 함수로 전달되는 값의 데이터 타입으로  i32를 명시하고 있습니다.  함수 마지막 줄에 값이나 변수를 적으면 해당 값이 함수를 호출한 곳으로 전달됩니다. 

fn fair_dice_roll() -> i32 {
    let v = 4;


    // 변수 v의 값이 main함수로 전달됩니다.
    v
}

fn main() {


    // fair_dice_roll 함수에서 전달된 값이 변수 ret에 저장됩니다.
    let ret = fair_dice_roll();

    println!("{}", ret);
}

 

실행결과

4



Rust에도 return문이 있습니다. 변수 또는 값을 적을 때 앞에 return 키워드를 적을 수 있습니다. 

fn fair_dice_roll() -> i32 {
    let v = 4;

    return v;
}

fn main() {
    let ret = fair_dice_roll();

    println!("{}", ret);
}



아래 예제에서 add 함수는 i32 (부호 있는 32비트 정수) 데이터 타입의 아규먼트 2개 x, y를 전달받고 한개의 i32 데이터 타입의 값 x+y 를 자신을 호출 함수로 전달합니다.

fn add(x: i32, y: i32) -> i32 {
    return x + y;
}

fn main() {

    // add 함수에 정수 1,2를 전달하여 계산결과 3을 다시 전달받습니다.
    println!("{}", add(1, 2));
}

 

실행결과

3



함수에서 튜플을 자신을 호출한 함수에 전달함으로써 여러 개의 값을 한번에 전달할 수 있습니다.

아래 예제에서 함수에서 전달되는 값의 데이터 타입을 i32 데이터 타입 두 개가 포함된 튜플로 지정하고 있습니다.

fn swap(x: i32, y: i32) -> (i32, i32) {
    return (y, x); // 전달 받은 변수의 순서를 바꾸어서 호출한 곳으로 다시 돌려줍니다.
}

fn main() {

    // 두 개의 정수 123, 321를 함수 swap에 전달하고 변수 result에 튜플을 전달받습니다.
    let result = swap(123, 321);


    // 튜플을 나누어 출력합니다.
    println!("{} {}", result.0, result.1);



    // 두 개의 정수 123, 321를 함수 swap에 전달하고 다시 전달받은 튜플을 두개의 변수에 나누어 대입합니다.
    let (a, b) = swap(123, 321);


    // 두개의 변수를 출력합니다.
    println!("{} {}", a, b);
}

 

실행 결과

321 123

321 123



함수에서 자신을 호출한 함수로 전달하는 값을 -> 다음에 데이터 타입으로 지정하지 않은 경우 비어있는 튜플을 리턴합니다.  비어 있는 튜플을 ()로 표현합니다. 

// 자신을 호출한 함수로 전달되는 값의 데이터 타입을 비어있는 튜플()로 명시한 경우
fn make_nothing() -> () {

    // return 문에 ()를 자신을 호출한 함수로 전달한다고 명시합니다.
    return ();
}

// 자신을 호출한 함수로 전달되는 값의 데이터 타입을 적지 않으면 -> ()를 생략한게 됩니다.
fn make_nothing2() {

    // return 문도 생략할 수 있습니다.
}

fn main() {

    let a = make_nothing();
    let b = make_nothing2();

    // 전달 받은 값을 출력해봅니다.
    println!("The value of a: {:?}", a);
    println!("The value of b: {:?}", b);
}

 

실행 결과

The value of a: ( )

The value of b: ( )

 

반응형

진행해본 결과물을 기록 및 공유하는 공간입니다.
잘못된 부분이나 개선점을 알려주시면 반영하겠습니다.


소스코드 복사시 하단에 있는 앵커 광고의 왼쪽 위를 클릭하여 닫은 후 해야 합니다.


문제가 생기면 포스트와 바뀐 환경이 있나 먼저 확인해보세요.
질문을 남겨주면 가능한 빨리 답변드립니다.


제가 쓴 책도 한번 검토해보세요 ^^

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기

댓글을 달아 주세요

TistoryWhaleSkin3.4">