1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! This crate provides a `recycle` extension method for Vec.
//! It's intended to change the type of the Vec while "recycling"
//! the underlying allocation. This is a trick that is useful especially
//! when storing data with short lifetimes in Vec:
//! ```
//! # use std::error::Error;
//! # use crate::recycle_vec::VecExt;
//! #
//! # struct Stream(bool);
//! #
//! # impl Stream {
//! #     fn new() -> Self {
//! #         Stream(false)
//! #     }
//! #
//! #     fn next(&mut self) -> Option<&[u8]> {
//! #         if self.0 {
//! #             None
//! #         } else {
//! #             self.0 = true;
//! #             Some(&b"foo"[..])
//! #         }
//! #     }
//! # }
//! #
//! # fn process(input: &[Object<'_>]) -> Result<(), Box<dyn Error>> {
//! #     for obj in input {
//! #         let _ = obj.reference;
//! #     }
//! #     Ok(())
//! # }
//! #
//! # struct Object<'a> {
//! #     reference: &'a [u8],
//! # }
//! #
//! # fn deserialize<'a>(input: &'a [u8], output: &mut Vec<Object<'a>>) -> Result<(), Box<dyn Error>> {
//! #     output.push(Object { reference: input });
//! #     Ok(())
//! # }
//! #
//! # fn main() -> Result<(), Box<dyn Error>> {
//! # let mut stream = Stream::new();
//! let mut objects: Vec<Object<'static>> = Vec::new();    // Any lifetime goes here
//!
//! while let Some(byte_chunk) = stream.next() {           // `byte_chunk` lifetime starts
//!     let mut temp: Vec<Object<'_>> = objects.recycle(); // `temp` lifetime starts
//!
//!     // Zero-copy parsing; deserialized `Object`s have references to `byte_chunk`
//!     deserialize(byte_chunk, &mut temp)?;
//!     process(&temp)?;
//!
//!     objects = temp.recycle();                          // `temp` lifetime ends
//! }                                                      // `byte_chunk` lifetime ends
//! # Ok(())
//! # }
//! ```
//! # Notes about safety
//! This crate uses internally `unsafe` to achieve it's functionality.
//! However, it provides a safe interface. To achieve safety, it does
//! the following precautions:
//! 1. It truncates the `Vec` to zero length, dropping all the values.
//! This ensures that no values of arbitrary types are transmuted
//! accidentally.
//! 2. It checks that the sizes and alignments of the source and target
//! types match. This ensures that the underlying block of memory backing
//! `Vec` is compatible layout-wise. The sizes and alignments are checked
//! statically, so if the compile will fail in case of a mismatch.
//! 3. It creates a new `Vec` value using `from_raw_parts`, instead of
//! transmuting, an operation whose soundness would be questionable.

#![no_std]

extern crate alloc;

use alloc::vec::Vec;

struct AssertSameLayout<A, B>(core::marker::PhantomData<(A, B)>);
impl<A, B> AssertSameLayout<A, B> {
    const OK: () = assert!(
        core::mem::size_of::<A>() == core::mem::size_of::<B>() && core::mem::align_of::<A>() == core::mem::align_of::<B>(),
        "types must have identical size and alignment"
    );
}

/// A trait that provides an API for recycling Vec's internal buffers
pub trait VecExt<T> {
    /// Allows re-interpreting the type of a Vec to reuse the allocation.
    /// The vector is emptied and any values contained in it will be dropped.
    /// The target type must have the same size and alignment as the source type.
    /// This API doesn't transmute any values of T to U, because it makes sure
    /// to empty the vector before any unsafe operations.
    fn recycle<U>(self) -> Vec<U>;
}

impl<T> VecExt<T> for Vec<T> {
    fn recycle<U>(mut self) -> Vec<U> {
        self.clear();

        () = AssertSameLayout::<T, U>::OK;

        let cap = self.capacity();
        let ptr = self.as_mut_ptr() as *mut U;
        core::mem::forget(self);
        unsafe { Vec::from_raw_parts(ptr, 0, cap) }
    }
}

/// Tests that `recycle` successfully re-interprets the type to have different lifetime from the original
#[test]
fn test_recycle_lifetime() {
    use crate::alloc::string::ToString;
    let s_1 = "foo".to_string();
    let mut buf = Vec::with_capacity(100);
    {
        let mut buf2 = buf;
        let s_2 = "foo".to_string();
        buf2.push(s_2.as_str());

        assert_eq!(buf2.len(), 1);
        assert_eq!(buf2.capacity(), 100);

        buf = buf2.recycle();
    }
    buf.push(s_1.as_str());
}

/// Tests that `recycle` successfully re-interprets the type itself
#[test]
fn test_recycle_type() {
    use crate::alloc::string::ToString;
    let s = "foo".to_string();
    let mut buf = Vec::with_capacity(100);
    {
        let mut buf2 = buf.recycle();

        let mut i = Vec::new();
        i.push(1);
        i.push(2);
        i.push(3);

        buf2.push(i.as_slice());

        assert_eq!(buf2.len(), 1);
        assert_eq!(buf2.capacity(), 100);

        buf = buf2.recycle();
    }
    buf.push(s.as_str());
}

#[test]
fn test_layout_assert() {
    let t = trybuild::TestCases::new();
    t.pass("tests/force_build.rs");
    t.compile_fail("tests/recycle_incompatible_size.rs");
    t.compile_fail("tests/recycle_incompatible_alignment.rs");
}