やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rust自習(単方向リスト2)

 生ポインタでnextを付け替えてみようとしたが、できなかった……。

成果物

生ポインタでnextを付け替えてみる

 前回、std::mem::replace関数を使ってNodenextを付け替えた。この関数はunsafe Rustにより生ポインタ操作することで所有権がらみの借用チェックを回避してくれる。その上unsafeな部分は隠蔽されているため、使う側はsafe Rustで書ける。

 今回は、自前で生ポインタ操作によりnextの付け替えをしてみたい。C言語のように。

前回コード

impl<T> Node<T> {
    fn new(item: T) -> Self { Self { item: item, next: None } }
    fn push(&mut self, item: T) {
        if self.next.is_none() {
            let new_node = Node::new(item);
            self.next = Some(Box::new(new_node));
        } else {
            let tmp_node = std::mem::replace(&mut self.next, None); // self.nextに一旦Noneを入れる
            let mut new_node = Node::new(item);
            new_node.next = tmp_node;
            self.next = Some(Box::new(new_node));
        }
    }
}

今回コード

    fn push(&mut self, item: T) {
//    fn push(mut self, item: T) {
        if self.next.is_none() {
            let new_node = Node::new(item);
            self.next = Some(Box::new(new_node));
        } else {
            let mut new_node = Node::new(item);
            let ptr_self_next = Box::into_raw(self.next.unwrap()); // error[E0507]: cannot move out of borrowed content
//            let ptr_self_next = Box::into_raw(Box::new(*self.next.unwrap())); // error[E0507]: cannot move out of borrowed content
            unsafe {
                new_node.next = Some(Box::from_raw(ptr_self_next));
            }
            self.next = Some(Box::new(new_node));
        }
    }
}

error[E0507]: cannot move out of borrowed content

    fn push(&mut self, item: T) {
        if self.next.is_none() {
            let new_node = Node::new(item);
            self.next = Some(Box::new(new_node));
        } else {
            let mut new_node = Node::new(item);
            let ptr_self_next = Box::into_raw(self.next.unwrap()); // error[E0507]: cannot move out of borrowed content
//            let ptr_self_next = Box::into_raw(self.next); // error[E0308]: mismatched types
//            let ptr_self_next = Box::into_raw(Box::new(*self.next.unwrap())); // error[E0507]: cannot move out of borrowed content
            unsafe {
                new_node.next = Some(Box::from_raw(ptr_self_next));
            }
            self.next = Some(Box::new(new_node));
        }
    }
}
error[E0507]: cannot move out of borrowed content
  --> src/lib.rs:15:47
   |
15 |             let ptr_self_next = Box::into_raw(self.next.unwrap()); // error[E0507]: cannot move out of borrowed content
   |                                               ^^^^^^^^^ cannot move out of borrowed content

 selfは借用されているので所有権を奪えない。

 だが、std::boxed::Box.into_raw()は所有権を奪うことでしか使えない。よって生ポインタが作れない……。

 なら、&mut selfmut selfにしちゃえばどうよ?

error[E0382]: borrow of moved value: first.next

    fn push(mut self, item: T) { // &mut self の`&`をとって所有権を奪うようにした。すると呼出`first.push()`後で`first`の所有権が失われる。
        if self.next.is_none() {
            let new_node = Node::new(item);
            self.next = Some(Box::new(new_node));
        } else {
            let mut new_node = Node::new(item);
            let ptr_self_next = Box::into_raw(self.next.unwrap()); // error[E0507]: cannot move out of borrowed content
            unsafe {
                new_node.next = Some(Box::from_raw(ptr_self_next));
            }
            self.next = Some(Box::new(new_node));
        }
    }
error[E0382]: borrow of moved value: `first.next`
  --> src/lib.rs:78:9
   |
72 |         let mut first = Node::new(0);
   |             --------- move occurs because `first` has type `Node<i32>`, which does not implement the `Copy` trait
...
76 |         first.push(1);
   |         ----- value moved here
77 |         assert_eq!(first.item, 0);
78 |         assert_eq!(first.next, Some(Box::new(Node { item: 1, next: None })));
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after move
   |
   = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

 pushを呼び出すと呼出元であるfirstの所有権が奪われる。その後assert_eq!で再びfirstを使ったためエラー。

 もちろん、このような用途を想定しているため、所有権を奪われては困る。

 だが、std::boxed::Box.into_raw()は所有権を奪うことでしか使えない。よって生ポインタが作れない……。

error[E0609]: no field item on type ()

 なら、mut selfで所有権を奪ったあと、戻り値でselfを返せば所有権を返せるのでは?

impl<T> Node<T> {
    // ...
    fn push_unsafe(mut self, item: T) -> Self {
        if self.next.is_none() {
            let new_node = Node::new(item);
            self.next = Some(Box::new(new_node));
        } else {
            let mut new_node = Node::new(item);
            let ptr_self_next = Box::into_raw(self.next.unwrap());
            unsafe {
                new_node.next = Some(Box::from_raw(ptr_self_next));
            }
            self.next = Some(Box::new(new_node));
        }
        self
    }

#[cfg(test)]
mod tests {
    use super::*;
    // ...
    #[test]
    fn Node_push_unsafe_not_last() {
        let mut first = Node::new(0);
        assert_eq!(first.item, 0);
        assert_eq!(first.next, None);

        let first = first.push(1);
        assert_eq!(first.item, 0);
        assert_eq!(first.next, Some(Box::new(Node { item: 1, next: None })));
        let second = first.next.as_ref().unwrap();
        assert_eq!(second.item, 1);
        assert_eq!(second.next, None);

        let first = first.push(2);
        assert_eq!(first.item, 0);
        assert_eq!(first.next, Some(Box::new(Node { item: 2, next: Some(Box::new(Node { item: 1, next: None })) })));
        let third = first.next.as_ref().unwrap();
        assert_eq!(third.item, 2);
        assert_eq!(third.next, Some(Box::new(Node { item: 1, next: None })));
    }
error[E0609]: no field `item` on type `()`
   --> src/lib.rs:146:26
    |
146 |         assert_eq!(first.item, 0);
    |                          ^^^^

 どうやら戻り値が()になっている模様。え、なにこの挙動。そういえば今までselfを返したことなんて無かった。何も返らなくなるのか……。つまり引数で参照でなく値を受け取ると、必ずメソッドの終端で死ぬってことか。大抵は奪われたら困ると思うのだが……。

 そうなるとライフタイム指定子も無駄だろう。なにせselfが最長寿命なのに、そいつがメソッド内で死んでしまうのだから。やはりpushでは所有権をうばうわけにはいかない。

結論

 生ポインタでnextの付け替えができなかった……。

 何か方法があるのか。あるいは、そんなコードが書けないから安全なのか。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで