読者です 読者をやめる 読者になる 読者になる

Rustのstd::mem::forgetがunsafeでない理由

Rustのstd::mem::forgetはunsafeではない。このことはAPIドキュメントに説明されている。Rustのオブジェクトは二重dropしてはいけないが、dropをせずに放置する分には(少なくともメモリ保護の観点からは)安全であり、 Drop を実装する側は drop が呼ばれなくても安全であるようにしなければならない。

これは、同じくドキュメントで説明されているように、容易に正当化できる。以下のように Rc で循環参照を作ると、 forget を模倣することができる。(std::mem::forget とは異なり、より多くのメモリリークを生じるという違いはある)

pub fn my_forget<T>(x: T) {
    use std::rc::Rc;
    use std::cell::RefCell;
    struct Cyc<T> {
        x: T,
        cyc: RefCell<Option<Rc<Cyc<T>>>>,
    }
    let c = Rc::new(Cyc {
        x: x,
        cyc: RefCell::new(None)
    });
    *c.cyc.borrow_mut() = Some(c.clone());
}

struct A<'a> {
    s: &'a str,
}

impl<'a> A<'a> {
    pub fn new(s: &'a str) -> Self {
        A { s: s, }
    }
}

impl<'a> Drop for A<'a> {
    fn drop(&mut self) {
        println!("dropped: {}", self.s);
    }
}

fn main() {
    let data1 = [49];
    let obj1 = A::new(std::str::from_utf8(&data1).unwrap());
    
    let data2 = [50];
    let obj2 = A::new(std::str::from_utf8(&data2).unwrap());
    std::mem::forget(obj2);
    
    let data3 = [51];
    let obj3 = A::new(std::str::from_utf8(&data3).unwrap());
    my_forget(obj3);
}

やや直感に反する点として、 forget<T>T: 'static を要求していないという点がある。例えば上の例でも、スタック上の文字列へのポインタが、循環参照の中に放置されたまま、プログラムが終了してしまっている。これはRustでは許されているようだ。