STUDY/Rust

Rust - 23. 고급 기능 (1) Unsafe RUST

sinawi95 2024. 3. 31. 11:13
728x90

얼마 안남았다~~~ 오예~~~ 3월내로 끝낼듯


1. Unsafe Rust

러스트가 보증하는 것의 일부를 거부하고 해당 보증을 수동으로 유지하는 것에 대한 책임을 지는 방법 

unsafe 키워드

  • 이 키워드를 사용한 블록은 안전하지 않은 코드(안전하지않은 슈퍼파워)를 넣을 수 있다.
    1. 원시포인터(raw pointer) 역참조
    2. 안전하지 않은 함수 혹은 메서드 호출
    3. 가변 정적 변수 접근 및 수정
    4. 안전하지 않은 트레이트 구현
    5. union 필드 접근
  • 위 다섯 가지 기능 제외한 다른 기능은 안전성 검사를 수행한다.
  • 안전하지 않은 코드는 유효한 방식으로 메모리에 접근하도록 보장해야한다.
  • unsafe 블록을 작게 만드는 편이 좋다.
    • 문제의 원인을 더 쉽게 추적할수 있음
  • 안전하지 않은 코드를 분리하려면 추상화 하고 api를 제공하는것이 가장 좋다.

1.1 원시포인터(raw pointer) 역참조

러스트 컴파일러는 참조가 항상 유효한것을 보장함

원시포인터

// *const T: 불변 원시 포인터
// *mut T: 가변 원시 포인터

// 원시 포인터 생성
fn main() {
  let mut num = 5;
  
  let r1 = &num as *const i32;
  let r2 = &mut un as *mut i32;
}
  • 생성할땐 unsafe 코드를 사용하지 않아도됨. 역참조 할 때만 사용

원시 포인터 특징

  • 원시 포인터는 대여 규칙을 무시할수 있음. 같은 위치에 대해 불변과 가변 포인터를 동시에 가질수 있거나 여러개의 가변포인터를 가질수 있음
    • 소유권 규칙에 따르면 가변 참조자와 불변 참조자를 동시에 허용하지 않음.
    • 원시포인터를 사용하면 동시에 사용할수 있으나 데이터 경합을 일으킬수있음.(unsafe)
  • 유효한 메모리를 가리키는 것을 보장하지 않음
  • null 이 될수 있음
  • 자동 메모리 정리를 구현하지 않음

1.2 안전하지 않은 함수 혹은 메서드 호출

// unsafe function
unsafe fn dangerous() {}


// call unsafe function
fn main() {  
  unsafe {
    dangerous()
  }
}
  • unsafe 함수는 분리된 unsafe 블록 내에서 호출되어야함. unsafe 블록 없으면 에러 발생

안전하지 않은 코드를 감싸는 안전한 추상화

use std::slice;

fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = values.len();
    let ptr = values.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

fn main() {
    let mut vector = vec![1, 2, 3, 4, 5, 6];
    let (left, right) = split_at_mut(&mut vector, 3);
    println!("{:?}", left);
    println!("{:?}", right);
}
  • 러스트 예제를 그대로 가져왔다.
  • 예제는 한 슬라이스에서 겹치지 않는 다른 부분을 접근하고있고 이를 반환해서 두개의 슬라이스를 만든다.
  • 러스트는 동일한 슬라이스에 두번 접근하는 것으로 판단해서 에러를 띄우므로 unsafe 블록이 필요하다.

 

FFI, Foreign Function Interface, 외래 함수 인터페이스

extern 키워드

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}
  • 외부(다른 언어로 쓰여진 프로그램)에서 만든 함수를 사용할수 있게 만듦 
  • 러스트에서 함수를 정의하고(함수이름, 입력, 출력) 사용할때 unsafe 블록 사용
#![allow(unused)]
fn main() {
    #[no_mangle]
    pub extern "C" fn call_from_c() {
        println!("Just called a Rust function from C!");
    }
}
  • 반대로 러스트 함수를 외부에서 쓸수 있도록 할수 있음
  • 이때는 unsafe 블록이 필요없음
  • 하지만 mangling 하지 않도록 #[no_mangle] 을 추가해야함

1.3 가변 정적 변수 접근 및 수정

static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}
  • 가변 정적변수(전역변수)를 사용하는 경우에 소유권 규칙 관련한 문제가 생길수 있음
    • 데이터경합이 있을수 있으므로 unsafe 블록 필요
  • 불변 정적변수는 상관없음

1.4 안전하지 않은 트레이트 구현

unsafe를 사용하여 안전하지 않은 트레이트를 구현할 수 있습니다. 메서드 중 하나 이상에 컴파일러가 확인할 수 없는 불변성 (invariant) 이 있는 경우 그 트레이트는 안전하지 않습니다. 예제 19-11에 표시된 것처럼 trait 앞에 unsafe 키워드를 추가하고 그 트레이트의 구현체도 unsafe로 표시함으로써 트레이트가 unsafe하다고 선언할 수 있습니다.

1.5 union 필드 접근

유니온 필드에 접근하는 것은 저장된 데이터 타입을 보장할수 없으므로 안전하지 않다.


https://doc.rust-kr.org/ch19-00-advanced-features.html

https://doc.rust-kr.org/ch19-01-unsafe-rust.html