ptr_ds/
maybe_uninit.rs

1//! 未初期化の値。
2//!
3//! ## Preliminaries
4//!
5//! 実行時の所望のタイミングで初期化されるような値を作りたいことがある。
6//! 次のような Safe Rust のコードはコンパイルエラーとなる。
7//!
8//! ```compile_fail
9//! let a = 123;
10//! let mut b;
11//! if a % 5 <= 3 { b = "foo"; }
12//! if a % 7 <= 4 { b = "bar"; }
13//! let _invalid = b; // CE
14//! ```
15//!
16//! [`MaybeUninit`][`std::mem::MaybeUninit`] を使うことで、下記のように書くことができる。
17//!
18//! ```
19//! use std::mem::MaybeUninit;
20//!
21//! let a = 123;
22//! let mut b_uninit = MaybeUninit::<&str>::uninit();
23//! let ptr = b_uninit.as_mut_ptr();
24//! if a % 5 <= 3 { unsafe { ptr.write("foo") } };
25//! if a % 7 <= 4 { unsafe { ptr.write("bar") } };
26//! let b = unsafe { b_uninit.assume_init() };
27//! assert_eq!(b, "bar");
28//! ```
29//!
30//! `.assume_init()` を呼んだ時点で実際には未初期化だった場合、当然未定義動作となる。
31//!
32//! ```ignore
33//! use std::mem::MaybeUninit;
34//!
35//! let a = MaybeUninit::<i32>::uninit();
36//! let _invalid = unsafe { a.assume_init() };
37//! ```
38//!
39//! ```text
40//!  --> src/lib.rs:6:25
41//!   |
42//! 6 | let _invalid = unsafe { a.assume_init() };
43//!   |                         ^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
44//! ```
45//!
46//! `MaybeUninit<T>` をメンバに持つ型 `U` に対する `MaybeUninit<U>` や、`[MaybeUninit<T>; N]`
47//! や、`Box<MaybeUninit<T>>` などの例を見てみる。
48//!
49//! ```
50//! use std::{mem::MaybeUninit, ptr};
51//!
52//! struct Foo {
53//!     vector: Vec<i32>,
54//!     string: MaybeUninit<String>,
55//! }
56//!
57//! let mut foo = unsafe {
58//!     let mut foo_uninit = MaybeUninit::<Foo>::uninit();
59//!     let ptr = foo_uninit.as_mut_ptr();
60//!     // (*ptr).vector = vec![]; // UB
61//!     ptr::addr_of_mut!((*ptr).vector).write(vec![0]);
62//!     foo_uninit.assume_init()
63//! };
64//! assert_eq!(foo.vector, [0]);
65//!
66//! let s_ptr = foo.string.as_mut_ptr();
67//! unsafe {
68//!     s_ptr.write("init".to_owned());
69//!     assert_eq!(foo.string.assume_init_ref(), "init");
70//!     drop(foo.string.assume_init_drop());
71//! }
72//! ```
73//!
74//! ```
75//! use std::mem::MaybeUninit;
76//!
77//! // let mut foo_uninit = [MaybeUninit::<String>::uninit(); 3]; // !Copy
78//! let mut foo_uninit = unsafe {
79//!     MaybeUninit::<[MaybeUninit<String>; 3]>::uninit().assume_init()
80//! };
81//! unsafe {
82//!     foo_uninit[0].write("uno".to_owned());
83//!     foo_uninit[1].write("dos".to_owned());
84//!
85//!     let foo = {
86//!         &*(&foo_uninit[..2] as *const [MaybeUninit<_>] as *const [String])
87//!     };
88//!     assert_eq!(foo[0], "uno");
89//!     assert_eq!(foo[1], "dos");
90//!
91//!     foo_uninit[0].assume_init_drop();
92//!     foo_uninit[1].assume_init_drop();
93//! }
94//! ```
95//!
96//! ```
97//! use std::{mem::MaybeUninit, ptr};
98//!
99//! struct Foo {
100//!     vector: Vec<i32>,
101//!     string: String,
102//! }
103//!
104//! let mut foo_uninit = Box::new(MaybeUninit::<Foo>::uninit());
105//! let foo: Box<Foo> = unsafe {
106//!     let ptr = foo_uninit.as_mut_ptr();
107//!     ptr::addr_of_mut!((*ptr).vector).write(vec![0]);
108//!     ptr::addr_of_mut!((*ptr).string).write("init".to_owned());
109//!     Box::from_raw(Box::leak(foo_uninit).as_mut_ptr())
110//! };
111//! assert_eq!(foo.vector, [0]);
112//! assert_eq!(foo.string, "init");
113//! ```
114//!
115//! レイアウトに関して、`T` と `MaybeUninit<T>` は同様の size および alignment を持つことは保証されている ([ref](https://doc.rust-lang.org/nightly/core/mem/union.MaybeUninit.html#layout-1))。
116//! `T` を含む型と `MaybeUninit<T>` を含む型が同様のレイアウトを持つとは限らないが、一旦は忘れておく。
117//!
118//! 未初期化な領域を含む状態で参照 `&mut T` を取得すると未定義動作となる。`ptr::addr_of_mut!`
119//! などを用いて回避する必要がある。
120//!
121//! ```
122//! use std::{mem::MaybeUninit, ptr};
123//!
124//! struct Foo {
125//!     vector: Vec<i32>,
126//!     string: String,
127//! }
128//!
129//! let mut foo = unsafe {
130//!     let mut foo_uninit = MaybeUninit::<Foo>::uninit();
131//!     let ptr = foo_uninit.as_mut_ptr();
132//!     // (*ptr).vector = vec![0]; // UB
133//!     ptr::addr_of_mut!((*ptr).vector).write(vec![0]);
134//!     // (*ptr).string = "init".to_owned(); // UB
135//!     ptr::addr_of_mut!((*ptr).string).write("init".to_owned());
136//!     (*ptr).string = "re-init".to_owned(); // OK
137//!     
138//!     foo_uninit.assume_init()
139//! };
140//! ```
141//!
142//! 初期化済みの領域に `.write()` するとメモリリークが起きる。
143//! あるいは、未初期化の領域を `=` で書き込もうとすると、`*mut T` の dereference なり
144//! `&mut T` の取得なりをする必要があるため未定義動作となる。
145//!
146//! ```
147//! use std::mem::MaybeUninit;
148//!
149//! struct Foo(MaybeUninit<String>);
150//!
151//! let mut foo = Foo(MaybeUninit::uninit());
152//! // unsafe { *foo.0.as_mut_ptr() = "init".to_owned() } // UB
153//! // unsafe { *foo.0.assume_init_mut() = "init".to_owned() } // UB
154//! foo.0.write("init once".to_owned());
155//!
156//! unsafe { *foo.0.as_mut_ptr() = "init twice".to_owned() } // OK
157//! unsafe { *foo.0.assume_init_mut() = "init thrice".to_owned() } // OK
158//! // foo.0.write("init".to_owned()); // memory leak
159//!
160//! unsafe { foo.0.assume_init_drop() }
161//! ```
162//!
163//! `*mut ()` であれば、未初期化 (dangling) の領域を dereference しても大丈夫。
164//!
165//! ```
166//! use std::mem::MaybeUninit;
167//!
168//! let unit_uninit = MaybeUninit::<()>::uninit();
169//! unsafe {
170//!     assert_eq!(unit_uninit.assume_init(), ());
171//! }
172//! ```
173
174#[cfg(test)]
175mod playground {
176    use std::{mem::MaybeUninit, ptr};
177
178    #[test]
179    fn nested() {
180        struct Foo {
181            i: i32,
182            s: String,
183            a: [MaybeUninit<i32>; 3],
184        }
185
186        let mut foo = unsafe {
187            let mut foo_uninit = MaybeUninit::<Foo>::uninit();
188            let ptr = foo_uninit.as_mut_ptr();
189            ptr::addr_of_mut!((*ptr).i).write(1);
190            // (*foo.as_mut_ptr()).s = "s".to_owned(); // UB
191            ptr::addr_of_mut!((*ptr).s).write("s".to_owned());
192            foo_uninit.assume_init()
193        };
194
195        foo.a[0].write(1);
196        assert_eq!(unsafe { foo.a[0].assume_init() }, 1);
197    }
198
199    #[test]
200    fn array() {
201        struct Foo<T, const N: usize> {
202            a: [MaybeUninit<T>; N],
203        }
204
205        let mut foo =
206            unsafe { MaybeUninit::<Foo<String, 3>>::uninit().assume_init() };
207
208        foo.a[0].write("one".to_owned());
209        // unsafe { *(foo.a[1].as_mut_ptr()) = "_".to_owned() }; // bad
210        // unsafe { *foo.a[1].assume_init_mut() = "_".to_owned() }; // also bad
211        foo.a[1].write("two".to_owned());
212
213        assert_eq!(unsafe { foo.a[0].assume_init_ref() }, "one");
214        assert_eq!(unsafe { foo.a[1].assume_init_ref() }, "two");
215
216        // foo.a[1].write("_".to_owned()); // bad
217        unsafe { *(foo.a[1].as_mut_ptr()) = "zwei".to_owned() }; // ok
218        unsafe { *foo.a[1].assume_init_mut() = "deux".to_owned() }; // also ok
219
220        let a = unsafe {
221            &mut *(&mut foo.a[..2] as *mut [MaybeUninit<_>] as *mut [String])
222        };
223        a[0] = "uno".to_owned();
224
225        let a = unsafe {
226            &*(&foo.a[..2] as *const [MaybeUninit<_>] as *const [String])
227        };
228        assert_eq!(a, ["uno", "deux"]);
229
230        unsafe { foo.a[0].assume_init_drop() };
231        unsafe { foo.a[1].assume_init_drop() };
232    }
233
234    #[test]
235    fn boxed() {
236        struct Foo {
237            a: String,
238        }
239
240        let mut foo_uninit = Box::new(MaybeUninit::<Foo>::uninit());
241        let foo = unsafe {
242            let ptr = foo_uninit.as_mut_ptr();
243            ptr::addr_of_mut!((*ptr).a).write("a".to_owned());
244            Box::from_raw(Box::leak(foo_uninit).as_mut_ptr())
245        };
246
247        assert_eq!(foo.a, "a");
248    }
249}