320x100
320x100

키워드

대부분의 언어와 마찬가지로 러스트에서만 사용하는 예약어

다른 언어들과 마찬가지로 키워드는 함수명이나 변수명으로 사용할 수 없으며 다양한 일들을 처리하기 위해 사용 가능

몇몇은 아직 아무 기능도 없지만 차후에 추가될 기능들을 위해 예약되어 있음

 

 

 

 

변수

let 키워드로 선언하며, 기본적으로 불변(immutable)임

이는 러스트가 제공하는 안정성과 쉬운 동시성을 활용하는 방식으로 코드를 작성할 수 있도록 하기 위한 너지(nudge / 슬며시 유도하기) 중 하나

 

- mut

변수를 가변하게 만들 수 있는 키워드. 변수명 앞에 붙혀서 사용

fn main() {
    let mut x = 5;
    println!("value: {x}");
    x = 6;
    println!("value: {x}");
}

 

 

 

 

상수

상수는 불변 변수와 비슷하지만, 값을 바꾸는 것이 허용되지 않음 (항상 불변)

상수는 let 키워드 대신 const 키워드로 선언하며 값의 타입은 반드시 명시되어야 함

상수는 전역 스코프를 포함한 어떤 스코프에서도 선언 가능하므로 코드의 많은 부분에서 알 필요가 있는 값에 유용

상수는 반드시 상수 표현식으로만 설정될 수 있기 때문에 런타임에서 계산될 수 있는 결과 값이 될 수 없음

// u32 = 32비트 정수형 타입
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

 

 

 

 

섀도잉

새 변수를 이전 변수명과 같은 이름으로 선언하는 것

첫 번째 변수가 두번 째 변수에 의해 가려졌다 (shadowed)는 의미로, 해당 변수의 이름을 사용할 때 컴파일러가 두 번째 변수를 보게 될 것이라는 의미

사실상 두번 째 변수는 첫 번째 것을 가려서 스스로를 다시 가리거나 스코프가 끝날 때까지 변수명의 사용을 가져갑ㅁ

let 키워드의 반복으로 정의

fn main() {
    let x = 5;
    let x = x + 1;
    
    {
        let x = x * 2;
        println!("inner scope value: {x}"); // 12
    }
    
    println!("value: {x}"); // 6
}

 

- mut와 섀도잉의 차이점

다시 let 키워드를 사용하여 새로운 변수를 만드는 것이기 때문에 같은 변수명으로 다른 타입의 값을 저장할 수 있음

let spaces = "    "; // 문자열 타입
let spaces = spaces.len(); // 숫자 타입

 

 

 

 

데이터 타입

러스트의 모든 값은 특정한 데이터 타입을 가짐.

러스트는 정적 타입 (statically typed) 언어로 모든 변수의 타입이 컴파일 시점에 반드시 정해져 있어야함

따라서 String에 parse를 사용하여 숫자로 변환하는 경우처럼 여러 가지 타입이 가능한 경우에는 다음과 같이 반드시 타입 명시를 추가해야함

let guess: u32 = "42".parse().expect("Not a Number");

 

 

 

 

 

스칼라 타입 (scalar)

하나의 값을 표현. 러스트는 정수, 부동소수점 숫자, 불리언(Boolean), 문자 이렇게 4가지 스칼라 타입을 가짐

 

 

정수형 (integer)

소수점이 없는 숫자. 비트 수에 따라 키워드가 다름

 

- 부호가 없는 경우 (양수만을 가지는 경우)

u8, u16, u32, u64, u128, usize (아키텍처에 따라 비트수를 가짐. 시스템이 64비트 아키텍처면 64비트)

부호가 없는 타입의 변수는 0에서 2^n-1 까지의 값을 저장 가능

> u8은 0부터 255까지의 값을 저장할 수 있음

 

- 부호가 있는 경우 (음수를 다룰 수 있는 경우) 

i8, i16, i32, i64, i128, isize (아키텍처에 따라 비트수를 가짐. 시스템이 64비트 아키텍처면 64비트)

부호가 있는 타입의 변수는 -(2^n-1) 부터 (2^n-1)-1 까지의 값을 저장 가능

> i8은 -128부터 127 사이의 값을 저장 가능

 

확실히 정해진 것이 아니라면 러스트의 기본 값인 i32가 일반적으로 좋은 시작 지점

isize나 usize는 주로 어떤 컬렉션 종류의 인덱스에 사용됨

 

 

정수형 리터럴

정수 값을 직접 표현한 고정된 숫자 값

let x = 98_222; // 10진수 (decimal)
let x = 0xff; // 16진수 (hexadecimal)
let x = 0o77; // 8진수 (octal)
let x = 0b1111_0000; // 2진수 (binary)
let x = b'A'; // 바이트(u8만)

 

 

정수 오버플로

러스트는 코드를 디버그 모드에서 컴파일 하는 경우 런타임에 정수 오버플로가 발생했을때 패닉(panic)을 발생시키는 검사를 포함함

--release 플래그를 사용하여 릴리스 모드로 컴파일하는 경우에는 정수 오버플로 검사를 실행 파일에 포함시키지 않음

대신 오버플로가 발생하면 2의 보수 감싸기를 수행하여 해당 타입이 가질 수 있는 최댓값보다 더 큰 값은 허용되는 최솟 값으로 돌아감

u8의 경우 256은 0, 257은 1이 되는 식

프로그램은 패닉을 발생시키지 않으나 예상하지 못했던 값을 가질 수 있음

명시적으로 오버플로를 다루기 위해서는 표준 라이브러리에서 기본 수치 타입에 대해 제공하는 메서드를 사용 가능

 

- wrapping_*

wrapping_add와 같은 메서드로 감싸기 동작 실행

 

- checked_*

오버 플로가 발생하면 None 값 반환

 

- overflowing_*

값과 함께 오버플로 발생이 있었는지 알려주는 불리언 값 반환

 

- saturating_*

값의 최댓값 혹은 최솟값 사이로 제한

 

 

부동소수점 타입 (floating-point)

러스트에서는 f32와 f64 키워드로 선언하며, 각각 32비트와 64비트의 크기를 가짐 (기본 타입은 f64. 현대 CPU 상에서 f64가 f32와 비슷한 속도를 내면서도 정밀하기 때문)

모든 부동소수점 타입은 부호를 가지며, IEEE-754 표준을 따름

f32 타입은 1배수 정밀도(single-precision)인 부동소수점이고 f64는 2배수 정밀도(double-precesion) 부동소수점

fn main() {
    let x = 2.0; // f64
    let y: f32 = 3.0; // f32
}

 

 

수치 연산

러스트는 모든 숫자 타입에 대해 기본 수학 연산 기능을 제공

정수 나눗셈의 경우 가장 가까운 정숫값으로 버림을 수행

fn main() {
    let sum = 5 + 10;
    let difference = 95.5 - 4.3;
    let product = 4 * 30;
    let quotient = 56.7 / 32.2;
    let truncated = -5 / 3; // -1
    let remander = 43 % 5;
}

 

 

불리언 타입 (boolean)

true와 false 두 값을 가지며 1바이트 크기. bool 키워드로 명시

let f: bool = false;

 

 

문자 타입 (charactor)

러스트의 기본적인 알파벳 타입. char 키워드로 선언

문자열 리터럴이 큰 따옴표 ""를 사용하는 반면, char 타입은 작은 따옴표 ''을 사용

러스트의 char 타입은 4바이트 크기이며 유니코드 스칼라 값을 표현 (ASCII 보다 훨씬 더 많은 값을 표현 가능)

억양 표시가 있는 문자, 한국어, 중국어, 일본어 문자, 이모지, 넓이가 0인 공백 문자 등을 지원

fn main() {
    let c = 'z';
    let z: char = 'Z';
    let heart = '♡';
}

 

 

 

 

복합 타입 (compound type)

여러 값을 하나의 타입으로 묶을 수 있는 타입

러스트에서는 튜플과 배열 두 가지 기본 복합 타입이 존재

 

 

튜플 (tuple)

다양한 타입의 여러 값을 묶어 하나의 복합 타입으로 만드는 일반적인 방법으로 고정된 길이를 가짐

즉, 한 번 선언되면 크기를 늘리거나 줄일 수 없음

튜플은 괄호 안에 쉼표로 구분하여 값들의 목록을 작성하여 정의

튜플 내의 각 위치는 타입을 가지며, 튜플 내의 타입들은 서로 다를 수 있음

let tup: (i32, f64, u8) = (500, 6.4, 1);
let tup = (500, 6.4, 1);
let (x, y, z) = tup; // 구조 해체 (destructuring)

let x: (i32, f64, u8) = (500, 6.4, 1);
let a = x.0; // 500;
let b = x.1; // 6.4
let c = x.2; // 1

 

- 유닛 (unit)

아무 값도 없는 튜플

이 값과 타입은 모두 ()로 작성되고 빈 값이나 비어있는 반환 타입을 나타냄

표현식이 어떠한 값도 반환하지 않는다면 암묵적으로 유닛 값을 반환

 

 

배열 (array)

튜플과는 달리 모든 요소가 같은 타입이어야 함

몇몇 언어들과 달리 러스트의 배열은 고정된 길이를 가짐

크기를 늘리거나 줄일 필요가 있다면 표준 라이브러리가 제공하는 벡터 타입을 사용

let a = [1, 2, 3, 4, 5];
let a: [i32, 5] = [1, 2, 3, 4, 5];
let a = [3; 5]; // 3으로 채워진 5개의 요소

let a = [1, 2, 3, 4, 5];
let first = a[0]; // 1
let second = a[1]; // 2

 

 

러스트는 기본적으로 명시한 인덱스가 배열 길이보다 작은지 검사하여 인덱스가 배열 길이보다 크거나 작을 경우 패닉을 일으키지만, 런타임에 사용자로 부터 값을 입력 받는 경우 즉시 실행을 종료하여 유효하지 않은 메모리로의 접근을 차단

 

 

 

 

함수 (function)

fn 키워드로 선언하고 이름은 스네이크 케이스로 작명 (변수 스네이크 케이스를 사용)

러스트에서는 함수의 작성 순서를 고려하지 않고, 호출하는 쪽에서 볼 수 있는 스코프 어딘가에 정의만 되어 있으면 됨

fn main() {
    println!("Hello");
    another_function();
}

fn another_function() {
    println!("Another function!");
}

 

 

매개변수 (parameter)

함수는 매개변수를 갖도록 정의될 수 있으며, 이는 함수 시그니처 (function signature)의 일부인 특별한 변수임

함수가 매개변수를 갖고 있으면 이 매개변수에 대한 구체적인 값을 전달할 수 있음

함수 시그니처에는 각 매개변수의 타입을 반드시 선언해야함

fn main() {
    another_function(5, 'h');
}

fn another_function(x: i32, z: char) {
    println!("value {x}, {z}"); // 5, h
}

 

 

구문 (statement)과 표현식(expression)

함수 본문은 필요에 따라 표현식으로 끝나는 구문의 나열로 구성됨

러스트는 표현식 기반의 언어로 구문과 표현식의 구분은 러스트를 이해하는데 중요한 개념

 

- 구문

어떤 동작을 수행하고 값을 반환하지 않는 명령

 

- 표현식

결괏값을 평가. 러스트 코드의 대부분이며, 표현식은 구문의 일부일 수 있음

 

fn main() {
    // let 구문의 일부로서 y에 바인딩
    let y = {  // 새로운 스코프 블록 (표현식)
        let x = 3;
        x + 1; // 표현식. 표현식에는 종결을 의미하는 세미콜론을 사용하지 않음. 추가할 경우 이는 구문으로 변경되면서 값을 반환하지 않게 됨
    }
}

 

 

반환 값을 갖는 함수

함수는 호출한 코드에 값을 반환 할 수 있음

반환되는 값을 명명해야할 필요는 없지만 그 값의 타입은 화살표 (->) 뒤에 선언되어야 함

러스트에서 함수의 반환 값은 함수 본문의 마지막 표현식의 값과 동일

return 키워드와 값을 지정하여 함수로부터 일찍 값을 반환 할 수 있지만, 대부분의 함수들은 암묵적으로 마지막 표현식 값을 반환

fn five() -> i32 {
    5
}

fn main() {
    let x = five();
    println!("value: {x}"); // 5
}

 

fn main() {
    let x = plus_one(5);
    println!("value: {x}"); // 6
}

fn plus_one(x: i32) -> i32 {
    x + 1 // 세미콜론을 붙혀서는 안된다
}

 

 

 

 

주석 (comment)

러스트에서 주석은 두 개의 슬래시로 시작하며 해당 줄의 끝까지 계속됨

한줄을 넘기는 주석의 경우에는 각 줄마다 //을 추가하여 표현

 

 

 

 

제어 흐름

어떤 조건이 참인지에 다라 특정 코드를 실행하고 어떤 조건을 만족하는 동안 특정 코드를 수행하는 기능

 

 

if 표현식

코드가 조건에 따라 분기할 수 있도록 해줌

러스트에서는 if 표현식의 조건식에는 반드시 명시적으로 불리언이 제공되어야 한다 (정수나 문자가 들어갈 경우 오류 발생)

fn main() {
    let number = 3;
    
    if number % 3 == 0 {
        println!("divisible by 3");
    } else if number % 2 == 0 {
        println!("dividible by 2");
    } else {
        println!("no divisible");
    }
}

 

- let 구문에서 if 사용

if 갈래와 else 갈래의 값 타입이 동일하지 않으면 오류 발생 (뼌수가 가질 수 있는 타입이 오직 하나이기 때문)

let condition true;
let number = if condition { 5 } else { 6 }; // if 갈래와 else 갈래의 타입이 동일해야함
println!("value {number}"); // 5

 

 

반복문 (loop)

- loop

명시적으로 중단 조건을 알려주기 전까지 무한히 반복수행

break 키워드로 멈추지 않으면 ctrl + c로 강제 종료할 떄까지 계속 반복 됨

어떤 스레드가 실행 완료되었는지 검사하는 등 실패할지도 모르는 연산을 재시도 할 때 등 사용

fn main() {
    let mut counter = 0;
    let result = loop {
        counter += 1;
        
        if counter == 10 {
            break counter * 2;
        }
    }
    println!("result: {result}"); // 20
}

 

- 루프 라벨 활용

루프 라벨을 추가적으로 명시하면 break나 continue를 특정 루프에 적용할 수 있음

루프 라벨은 반드시 작은 따옴표로 시작해야함

fn main() {
    let mut count = 0;
    'counting_up: loop {
        let mut remaining = 10;
        
        loop {
            if remaining == 9 {
                break; // 바로 바깥 쪽 loop 종료
            }
            
            if count == 2 {
                break 'counting_up; // counting_up loop 종료
            }
            remaining -= 1;
        }
        count += 1;
    }
    
    println!("end count = {count}"); // 2
}

 

 

while

조건문이 true 인 동안 계속 반복되는 반복문

loop와 if, else, break 조합 없이 간단하게 동일한 로직 구현 가능

fn main() {
    let mut number = 3;
    
    // number가 0이 아닌 동안 계속 실행
    while number != 0 {
        number -= 1;
    }
}

 

 

for

while의 경우 인덱스의 길이가 부정확하면 패닉을 발생시킬 수 있음

이러한 상황에 대한 대안으로 컬렉션의 각 아이템에 대하여 임의의 코드를 실행시킬 수 있또록 for문을 사용

fn main() {
    let a = [10, 20, 30, 40, 50];
    
    for element in a {
        println!("value: {element}"); // 10, 20, 30, 40, 50
    }
}
fn main() {
    // rev = 범위값을 역순으로 만듦
    for number in (1..4).rev() {
        println!("{number}"); // 4, 3, 2, 1
    }
}
300x250
728x90