Module nekolib_doc::discussion::ptr_ds::rawptr
source · Expand description
生ポインタ。
§Preliminaries
§参照とポインタの作成
Safe Rust においては、下記のようなコードはコンパイルエラーとなる。
let mut a = 1;
let mut_ref_1 = &mut a;
let mut_ref_2 = &mut *mut_ref_1;
*mut_ref_2 = 2;
*mut_ref_1 = 3;
let _invalid = *mut_ref_2; // (!)
Unsafe Rust において、生ポインタを使うことで、コンパイルを通すことはできる。
let mut a = 1_u32;
let mut_ref_1 = &mut a;
let mut_ref_2 = unsafe { &mut *(mut_ref_1 as *mut u32) };
*mut_ref_2 = 2;
*mut_ref_1 = 3;
let _invalid = *mut_ref_2; // (?)
ただし、(たとえば C++ がそうであるように)コンパイルが通り、プログラムが正常終了したというのは、 コードに問題がないということの証明にはならない。実際、Miri でテストすることで未定義動作が検出され、 次のような出力が得られるであろう。
% cargo miri test
error: Undefined Behavior: attempting a read access using <90194> at alloc23256[0x0], but that tag does not exist in the borrow stack for this location
--> src/lib.rs:8:20
|
8 | let _invalid = *mut_ref_2; // (?)
| ^^^^^^^^^^
| |
| attempting a read access using <90194> at alloc23256[0x0], but that tag does not exist in the borrow stack for this location
| this error occurs as part of an access at alloc23256[0x0..0x4]
こうした検出は Stacked Borrows と呼ばれる機構によって行われている。 Miri では Stacked Borrows のサポートは experimental とのことであるが、一旦はこれを信用することにする。
さて、safe なスマートポインタとして Box
があるので、それを使ってみよう。
struct Foo(u32);
impl Foo {
pub fn new() -> Self { Foo(0) }
}
let mut foo = Box::new(Foo::new());
assert_eq!(foo.0, 0);
foo.0 = 10;
assert_eq!(foo.0, 10);
Box::leak
を用いることで &'a mut T
を取り出すことができる。
let foo = Box::new(Foo::new());
let foo_mut_ref = Box::leak(foo);
assert_eq!(foo_mut_ref.0, 0);
foo_mut_ref.0 = 10;
assert_eq!(foo_mut_ref.0, 10);
// (?)
&'a mut T
は、生ポインタ (raw pointer) *mut T
にキャストすることもできる。
参照外し (dereference) は unsafe
となる。
let foo = Box::new(Foo::new());
let foo_mut_ptr: *mut _ = Box::leak(foo);
assert_eq!(unsafe { (*foo_mut_ptr).0 }, 0);
unsafe { (*foo_mut_ptr).0 = 10 };
assert_eq!(unsafe { (*foo_mut_ptr).0 }, 10);
// (?)
ところで、スマートポインタから生ポインタを取り出したままだと、drop されないのでメモリリークしてしまう。 これも Miri によって検出でき、次のような出力が得られるであろう。
error: memory leaked: alloc23417 (Rust heap, size: 4, align: 4), allocated here:
--> .../lib/rustlib/src/rust/library/alloc/src/alloc.rs:98:9
|
98 | __rust_alloc(layout.size(), layout.align())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Box::into_raw
にある例に従い、次のようにすることで、Box
側に destructor を呼ばせることができる。
逆に、勝手に destructor を呼ばれると困るような状況においては、Box
を使うと厄介なことになる。
let foo_mut_ptr: *mut _ = Box::leak(Box::new(Foo::new()));
unsafe { drop(Box::from_raw(foo_mut_ptr)) };
*mut T
の代わりに std::ptr::NonNull
を用いることもできる。
use std::ptr::NonNull;
let foo_mut_ref = Box::leak(Box::new(Foo::new()));
let foo_nonnull = NonNull::from(foo_mut_ref);
unsafe { drop(Box::from_raw(foo_nonnull.as_ptr())) };
*mut T
に対する NonNull
の違いは、null でないことの他に covariant
であることが挙げられるが、これに関しては variance
を参照せよ。