X

曜彤.手记

随记,关于互联网技术、产品与创业

吉 ICP 备10004938-2号

《The Rustonomicon》读书笔记


“The Dark Arts of Unsafe Rust”。

Chapter 1 - Meet Safe and Unsafe

  1. (Page:3)可以使用编译器标记 #![forbid(unsafe_code)] 来表明仅允许使用安全(非 unsafe)的 Rust 语言特性。
  2. (Page:4)为何可以在 Rust 中引用临时值(即 C++ 中的“右值”)?
let x = &mut 0;
// Usually a temporary would be dropped by now, but the temporary for `0` lives to the end of the block.
*x = 1;
println!("{}", x);  // 1.
  1. (Page:4)Rust 中一些常见的 unsafe 函数或 trait
fn main() {
    let x = [1, 2, 4];
    unsafe {
        assert_eq!(x.get_unchecked(1), &2);
    }
}
fn main() {
    fn foo() -> i32 {
        0
    }
    // "*const ()" is similar to "const void*" in C/C++.
    let pointer = foo as *const ();
    let function = unsafe {
        std::mem::transmute::<*const (), fn() -> i32>(pointer)
    };
    assert_eq!(function(), 0);
}
fn main() {
    let s: &str = "123";
    let ptr: *const u8 = s.as_ptr();

    unsafe {
        println!("{}", *ptr.offset(1) as char);
        println!("{}", *ptr.offset(2) as char);
    }
}
  1. (Page:6)unsafe 的能力:
  1. (Page:9)unsafe 操作的合理性必然取决于通过其他 “safe” 操作建立的状态。

Chapter 2 - Data Representation in Rust

  1. (Page:10)对齐
struct A {
  a: u8,
  b: u32,
  c: u16,
}
// after alignment (the fields ordering may be changed):
// struct A {
//   a: u8,
//   _pad1: [u8; 3],
//   b: u32,
//   c: u16,
//   _pad2: [u8; 2],
// }
enum Foo {
    A(u32),
    B(u64),
    C(u8), 
}
// layout in memory:
// struct FooRepr {
//     data: u64, // this is either a u64, u32, or u8 based on `tag`. 
//     tag: u8, // 0 = A, 1 = B, 2 = C.
// }
assert_eq!(core::mem::size_of::<Option<&T>>(), core::mem::size_of::<&T>());
  1. (Page:13)特殊大小类型:

- 动态大小类型(DSTs)

// custom DST (limited usage).
// making the type generic and performing an "unsizing coercion".
struct MySuperSliceable<T: ?Sized> {
    info: u32,
    data: T,
}
fn main() {
    let sized = MySuperSliceable {
        info: 17,
        data: [0; 8],
    };
    let dynamic = &sized;
    // prints: "17 [0, 0, 0, 0, 0, 0, 0, 0]".
    println!("{} {:?}", dynamic.info, &dynamic.data);
}

- 零大小类型(ZSTs)

struct Noting;  // no fields = no size.

// all fields have no size = no size.
struct LotsOfNothing {
    foo: Nothing,
    qux: (),      // empty tuple has no size.
    baz: [u8; 0], // empty array has no size.
}

- 空类型

enum Void {}

let res: Result<u32, Void> = Ok(0);

// Err doesn't exist anymore, so Ok is actually irrefutable.
let Ok(num) = res;
  1. (Page:17)可选的数据布局形式(repr):

- repr(C)

#[repr(C)]
pub struct Rect { x: f32, y: f32, width: f32, height: f32 }

- repr(transparent)

- repr(u?)repr(i?)

- repr(packed)

- repr(align(n))

Chapter 3 - Ownership and Lifetimes

  1. (Page:21)当变量或指针在同一时间指向了同一块发生重叠的内存区域时,即可称它们发生了 alias。由于 Rust 的所有权机制可以避免这种情况的发生,因此编译器也可以进行相应的优化:
    • 对于某些值,当其没有被指针引用时,可以被存放到寄存器中;
    • 通过证明在上一次读操作后,内存没有被改变,来减少一些无用的内存读操作(多次读合并为一次);
    • 通过证明某块内存在下一次写入之前永远不会被读取,来消除重复无用的内存写操作(多次写合并为一次);
    • 对内存的读写操作进行重排序,前提是它们彼此并不相互依赖。

- 优化前

// before optimization.
fn compute(input: &u32, output: &mut u32) {
    if *input > 10 {
        *output = 1; 
    }
    if *input > 5 { 
        *output *= 2;
    } 
}

- 优化后(前提:引用 input 与 output 没有发生 aliasing):

// after optimization.
fn compute(input: &u32, output: &mut u32) {
    let cached_input = *input; // keep the value of *input in a register. 
    if cached_input > 10 {
        *output = 2; // x > 10 implies x > 5, so double and exit immediately.
    } else if cached_input > 5 {
        *output *= 2; 
    }
}
  1. (Page:23)Lifetimes 基本:

- Rust 代码

let x = 0; 
let z;
let y = &x; 
z = y;

- 对应 desugar 后的情况(形式化语法):

'a: {
    let x: i32 = 0;
    'b: {
        let z: &'b i32;
        'c: {
            // Must use 'b here because this reference is being passed to that scope.
            let y: &'b i32 = &'b x;
            z = y;
        }   
    }
}
  1. (Page:27)一个 aliasing 导致 lifetime 出错的例子:

- Rust 代码

fn main() {
    let mut data = vec![1, 2, 3]; 
    let x = &data[0]; 
    data.push(4);  // &mut self -> introduces a lifetime scope.
    println!("{}", x);
}

- 对应 desugar 后的情况

'a: {
    let mut data: Vec<i32> = vec![1, 2, 3]; 
    'b: {
        // 'b is as big as we need this borrow to be, just need to get to `println!`.
        let x: &'b i32 = Index::index::<'b>(&'b data, 0); 
        'c: {
            // temporary scope because we don't need the &mut to last any longer.
            Vec::push(&'c mut data, 4);
        }
        println!("{}", x);
    }
}
  1. (Page:28)lifetime 作用域:
#[derive(Debug)]
struct X<'a>(&'a i32);  // a reference to outter value.
impl Drop for X<'_> {
    fn drop(&mut self) {}
}
let mut data = vec![1, 2, 3]; 
let x = X(&data[0]); 
println!("{:?}", x); 
data.push(4);
// here, the destructor is run and therefore this'll fail to compile.
let mut data = vec![1, 2, 3];
// This mut allows us to change where the reference points to.
let mut x = &data[0];
println!("{}", x);  // Last use of this borrow.
data.push(4);
x = &data[3];  // We start a new borrow here.
println!("{}", x);
  1. (Page:29)lifetime 的局限性:下述代码从引用的语义上来看,没有产生 aliasing 的问题,但实际 borrow-checker 在进行类似 desugar 之后的检查分析时,仍会检测到 aliasing,进而阻止编译通过。这是由于:Rust 编译器并不理解“可变借用(&mut self)”已不再需要,而是选择保守地将其可用范围扩展到整个当前的 lifetime 作用域

- Rust 代码

#[derive(Debug)]
struct Foo;
impl Foo {
    fn mutate_and_share(&mut self) -> &Self { &*self } 
    fn share(&self) {}
}
fn main() {
    let mut foo = Foo;
    let loan = foo.mutate_and_share(); 
    foo.share();
    println!("{:?}", loan);
}

- 对应 desugar 后的情况

struct Foo;
impl Foo {
    fn mutate_and_share<'a>(&'a mut self) -> &'a Self { &'a *self } 
    fn share<'a>(&'a self) {}
}
fn main() {
    'b: {
        let mut foo: Foo = Foo; 
        'c: {
            // compiler will assumes "&'c mut foo" will be alive within the whole 'c.
            let loan: &'c Foo = Foo::mutate_and_share::<'c>(&'c mut foo); 
            'd: {  // aliasing occurs.
                Foo::share::<'d>(&'d foo);
            }
            println!("{:?}", loan);
        }
    } 
}
  1. (Page:32)Unbounded Lifetimes
// unbound lifetime, will be lifted to 'static by default.
fn get_str<'a>() -> &'a str {
    return &"123";
}
  1. (Page:33)HRTBs(Higher-Rank Trait Bounds):
struct Closure<F> {
    data: (u8, u16),
    func: F, 
}
impl<F> Closure<F>
    // HRTB -> for all choices of 'a.
    where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8, {
    fn call<'a>(&'a self) -> &'a u8 {
        (self.func)(&self.data) 
    }
}
fn do_it<'b>(data: &'b (u8, u16)) -> &'b u8 { &data.0 }

fn main() {
    let clo = Closure { data: (0, 1), func: do_it };
    println!("{}", clo.call()); 
}
  1. (Page:42)Variance

  1. (Page:43)Drop Checker
  1. (Page:49)PhantomData
use std::marker;
struct Iter<'a, T: 'a> {
    ptr: *const T,
    end: *const T,
    _marker: marker::PhantomData<&'a T>,
}
use std::marker;

struct Vec<T> {
    data: *const T, // *const for covariance!
    len: usize,
    cap: usize,
    _marker: marker::PhantomData<T>,  // !!! Vec owned T !!!
}

Chapter 4 - Type Conversions

  1. (Page:57)Casts
  1. (Page:60)Transmutes

Chapter 5 - Working With Uninitialized Memory

  1. (Page:62)未初始化内存
fn main() {
    let x: i32;
    // println!("{}", x);  // error occurs!
}
fn main() {
    let x: i32;
    if true { 
        x = 1;
    }
    // println!("{}", x);  // error occurs!
}
let x: i32;
loop {
    // Rust doesn't understand that this branch will be taken unconditionally, // because it relies on actual values.
    if true {
        // but it does understand that it will only be taken once because // we unconditionally break out of it. Therefore `x` doesn't
        // need to be marked as mutable.
        x = 0;
        break; 
    }
}
// it also knows that it's impossible to get here without reaching the break. // And therefore that `x` must be initialized here!
println!("{}", x);
  1. (Page:66)未初始化实例:
use std::mem::{self, MaybeUninit};

fn main() { 
    const SIZE: usize = 10;
    let x = {
        // create an uninitialized array of `MaybeUninit`. The `assume_init` is 
        // safe because the type we are claiming to have initialized here is a 
        // bunch of `MaybeUninit`s, which do not require initialization.
        let mut x: [MaybeUninit<Box<u32>>; SIZE] = unsafe {
            MaybeUninit::uninit().assume_init() 
        };
        // dropping a `MaybeUninit` does nothing. Thus using raw pointer
        // assignment instead of `ptr::write` does not cause the old
        // uninitialized value to be dropped.
        // exception safety is not a concern because Box can't panic. 
        for i in 0..SIZE {
            x[i] = MaybeUninit::new(Box::new(i as u32)); 
        }
        // everything is initialized. Transmute the array to the initialized type.
        unsafe { mem::transmute::<_, [Box<u32>; SIZE]>(x) }
    };
    dbg!(x);
}

(不定期更新)



这是文章底线,下面是评论
  暂无评论,欢迎勾搭 :)