🦀 Rust

[Rust 입문] 2. 변수, 함수 기초

date
Mar 23, 2023
slug
rust-var-func
author
status
Public
tags
Rust
독학
summary
Rust에서 변수와 함수를 정의하는 방법입니다.
type
Post
thumbnail
category
🦀 Rust
updatedAt
Mar 23, 2023 11:18 AM

변수의 선언

rust는 변수의 선언을 let 키워드를 이용해서 선언한다.
let i = 10; let f = 3.14; let c = 'a'; let i_type: u32 = 1_000_000; let f_type: f32 = 3.14; let c_type: char = 'a'; // let [변수명] = 값 // let [변수명]: [타입명] = 값
위처럼 변수에 리터럴을 대입하는 형태로 선언할 수 있다. 리터럴을 직접 대입하면 컴파일러가 알아서 타입을 추론하지만 변수의 타입을 적을수도 있다.

rust의 변수 타입

rust의 변수 타입은 Scalar (자바에서 프리미티브 타입이라 부르는 것과 유사한) 타입과 Compound 타입이 있다.
Scalar와 Primitive의 차이는 문맥상 유사하게 사용될 수 있지만 분명 다른 값이다. Scaler ↔ Compound 관계는 쪼갤 수 없는 단일 값과 단일 값들이 뭉친 복합적인 값을 의미한다. Primitive ↔ Reference 관계는 Primitive 타입 자체로써 값을 가지는 것을 의미하며 Reference는 메모리에 저장된 다른 값을 참조하는 변수를 의미한다. 참조 - https://stackoverflow.com/questions/6623130/scalar-vs-primitive-data-type-are-they-the-same-thing

Scalar Type

  • 정수 타입
    • 부호가 있으면 접두사로 i가 붙으며 부호가 없으면 u가 붙는다.
    • 8비트, 16비트, 32비트, 64비트, 128비트, cpu의 아키텍쳐에 의존적인 비트(size)를 지원한다.
    • 예 : u128 (부호 없는 128비트 정수), i8 (부호 있는 - 2의 보수를 따르는 8비트 정수), usize (64비트 컴퓨터 환경에서는 64비트 크기의 부호없는 정수)
    • 리터럴은 10, 16 (0x 접두사), 8 (0o 접두사), 2(0b 접두사)진수 표기법과 바이트(b'[아스키 문자열])를 지원한다.
    • 예 : 1_000_000 , 0b10101010, b'a'
  • 부동 소수점 (IEEE 754) 타입
    • 부종 소수점은 f32 , f64 만 사용한다. (32와 64는 비트 수이다.)
  • 논리 타입
    • bool 을 사용하며 리터럴은 true와 false가 있다.
  • 문자 타입
    • char 로 표기한다.
    • 문자 타입은 rust가 다른 언어와 좀 다른 특성이 있는데 rust에서 문자는 유니코드 타입으로 저장한다. 그래서 1바이트가 아니다.
    • 기존 아스키 문자나 이모지, 한자, 한국어(한 글자)를 char로 저장할 수 있다.

Compound Type

rust에서 컴파운드 타입은 여러개의 스칼라 타입으로 이루어진 타입을 의미한다.
  • Tuple
    • 튜플은 여러가지 타입을 하나의 타입으로 묶는 데 주로 사용한다.
    • 괄호로 여러 타입을 묶어서 정의한다.
  • 배열
    • 동일 타입으로만 구성되어 있다.
    • 대괄호로 정의한다.
let tup: (i32, char) = (2021, '😈'); println!("year : {}, emoji : {}", tup.0, tup.1); let (tup_year, tup_emoji) = tup; println!("tup_year : {}, tup_emoji : {}", tup_year, tup_emoji); let arr_ten: [i32; 11] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let arr_ten_1 = [0i32; 11]; let mut index = 2; println!("arr_ten[{}] : {}", index, arr_ten[index]); index = 20; println!("arr_ten[-1] : {}", arr_ten[index]);

immutable

rust에서 변수는 기본적으로 불변성 (immutable) 속성을 가지고 있다. 그래서 한번 선언된 변수를 함부로 변경할 수 없다.
rust에서 이는 안정성과 동시성 문제를 해결하기 위한 방법이 된다.

mutable

사정상 변수를 런타임 중간중간에 변경하고 싶으면 mut 키워드를 붙이면 된다. 하지만 rust는 버그 방지를 위해 불변성 변수를 권장한다.
// 불변성을 지닌 let 선언 let val_immut_x = 10; println!("val_immut_x : {}", val_immut_x); // 가변성을 지닌 let 선언 let mut val_mut_x = 5; println!("val_mut_x (before) : {}", val_mut_x); val_mut_x = 6; println!("val_mut_x (after) : {}", val_mut_x);

불변성과 상수의 차이

rust는 const라는 키워드를 제공해주어 상수 선언도 지원한다. 하지만 rust에서 일반 변수와 상수의 차이가 혼동이 되는데 어떤 차이가 있냐면
  • 상수를 선언할 때에는 반드시 값의 타입을 같이 선언해야 한다.
  • 상수의 명칭은 모두 대문자로 선언하여야 한다.
  • 상수는 어느 영역에서도 선언될 수 있다. (글로벌 영역에서도 선언 가능하다.)
  • 상수는 쉐도잉 (뒤에 설명)이 불가능하다.
  • 상수의 값은 컴파일 타임에 결정되어야 한다.
rust의 const 키워드는 C++의 constexpr과 비슷하다.
const CONST_MAX: u32 = 1024; println!("CONST_MAX : {}", CONST_MAX);

쉐도잉

rust는 C/C++이나 자바같은 언어와 다르게 동일한 명칭의 변수를 동일한 코드영역에서 여러 번 선언할 수 있는데 이것을 쉐도잉이라고 한다.
쉐도잉을 할 경우 기존 값을 가리고 (그래서 쉐도잉이라는 표현을 쓴다.) 새로 정의한 값을 가리킨다.
변수를 mutable하게 정의하여 중간중간에 바꾸는 행동은 변수 값을 바꾸는 것이라 변수의 타입을 변경시킬 수 없으며 쉐도잉은 변수의 동일한 이름의 변수를 새로 추가하는 것이지 (기존 이름의 변수가 잠시 가려지는 것) 기존 변수를 변경하는 것이 아닌 차이점이 존재한다. (https://stackoverflow.com/questions/40621325/why-do-i-need-rebinding-shadowing-when-i-can-have-mutable-variable-binding - 쉐도잉과 가변 변수의 차이)
아래의 spaces 변수는 원래 공백이라는 의미를 가지고 있고 공백 문자열에 바인딩 되었지만 이후에 공백 문자열의 길이로 쉐도잉 된다.
let spaces = " "; println!("spaces : {}", spaces); let spaces = spaces.len(); println!("spaces : {}", spaces);
아래에서 spaces라는 변수는 블럭 내에서 의미가 잠시 바뀌었다가 블럭 밖으로 빠져나오면 다시 원래 의미를 가진다.
let spaces = " "; { println!("spaces : {}", spaces); let spaces = spaces.len(); println!("spaces : {}", spaces); } println!("spaces : {}", spaces);

함수의 정의

rust에서는 함수의 정의 키워드가 fn이다. 함수의 정의 위치를 신경쓰지 않으며 위에 정의하거나 밑에 정의하거나 상관 없다.
함수의 인자는 매개변수나 전달인자라고 부르며 매개변수가 있는 함수는 매개변수에 대한 타입을 정의하여야 한다.
함수의 리턴값이 존재할때엔 리턴값에 대한 타입을 정의해야 한다.
리턴값이 존재하는 함수의 본문은 표현식으로 종결되는 문장의 나열로 종결되어야 한다. 그래서 return 키워드를 하나만 사용할 때에는 표현식으로 종결된다면 return 키워드를 사용할 필요가 없다.

문장과 표현식

문장(구문)은 무엇인가 동작을 수행하지만 리턴값이 존재하지 않는다. 하지만 표현식은 식의 리턴값이 존재한다.
let x = 5; // 여기서 "let x = 5;"는 구문이며 구문의 부분인 리터럴 "5" 는 표현식이다. let y = { // 중괄호 전체가 표현식이 된다. let x = x + 1; // 구문이다. x * 2 // 표현식이다. 세미콜론을 붙이면 구문으로 바뀐다. }; println!("x : {}, y : {}", x, y);

함수 정의 예

fn subfunc() { println!("hello my name is subfunc!"); } fn subfunc_arg(arg: i32) { println!("arg : {}", arg); } fn is_odd(arg: u64) -> bool { // 이렇게 리턴되는 값이 있을 경우 화살표와 형식을 적어주어야 한다. arg % 2 == 1 } fn rtntest(arg: i64) -> i64 { if arg > 10 { return 230; // 중간에 리턴해야 할 경우엔 return 키워드로 리턴한다. } 100 }