diplomat_runtime/
write.rs

1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::ffi::c_void;
4use core::{fmt, ptr};
5
6/// An object that can one can write UTF-8 strings to
7///
8/// This allows the C API to write to arbitrary kinds of objects, for example a
9/// C++ std::string or a char buffer.
10///
11/// The way to use this object is to fill out the `buf`, `len`, `cap` fields with
12/// appropriate values for the buffer, its current length, and its current capacity,
13/// and `flush` and `grow` with appropriate callbacks (using `context` to reference any
14/// state they need). This object will be passed by mutable reference to the Rust side,
15/// and Rust will write to it, calling `grow()` as necessary. Once done, it will call `flush()`
16/// to update any state on `context` (e.g. adding a null terminator, updating the length).
17/// The object on the foreign side will be directly usable after this, the foreign side
18/// need not perform additional state updates after passing an [`DiplomatWrite`] to
19/// a function.
20///
21/// May be extended in the future to support further invariants
22///
23/// DiplomatWrite will not perform any cleanup on `context` or `buf`, these are logically
24/// "borrows" from the FFI side.
25///
26/// # DiplomatWrite is Polymorphic
27///
28/// Instances of [`DiplomatWrite`] can be created from multiple different sources.
29/// There are two constructors available in `diplomat_runtime`:
30///
31/// 1. [`diplomat_simple_write()`] to write to a fixed-size buffer.
32/// 2. [`diplomat_buffer_write_create()`] to write to a Vec allocated by Rust.
33///    A wrapper is provided: [`RustWriteVec`](rust_interop::RustWriteVec).
34///
35/// Backends may have additional constructors for writing to various shapes of buffer.
36///
37/// ⚠️ Because [`DiplomatWrite`] is polymorphic, the destructor must know how the instance
38/// was constructed. It is therefore unsound to use functions such as [`core::mem::swap`]
39/// on instances of [`DiplomatWrite`] with potentially different sources. For example,
40/// the following code is not safe:
41///
42/// ```no_run
43/// use diplomat_runtime::DiplomatWrite;
44/// fn bad(write: &mut DiplomatWrite) {
45///   let mut some_other_write: DiplomatWrite = unimplemented!();
46///   // Not safe! The two `DiplomatWrite`s have different invariants
47///   core::mem::swap(write, &mut some_other_write);
48/// }
49/// ```
50///
51/// As a result, any function that returns an owned `DiplomatWrite` or a `&mut DiplomatWrite`
52/// must be `unsafe`. For an example, see [`RustWriteVec::borrow_mut`].
53///
54/// Diplomat backends guarantee they will only ever hand the same type of `DiplomatWrite` object to Rust
55/// code; this is only something you need to worry about if using [`RustWriteVec`](rust_interop::RustWriteVec),
56/// or `DiplomatWrite` objects manually created in Rust via APIs like `diplomat_simple_write`.
57///
58/// # Safety invariants:
59///  - `flush()` and `grow()` will be passed `self` including `context` and it should always be safe to do so.
60///    `context` may be  null, however `flush()` and `grow()` must then be ready to receive it as such.
61///  - `buf` must be a valid pointer to `cap` bytes of memory
62///  - `buf` must point to `len` consecutive properly initialized bytes
63///  - `cap` must be less than or equal to isize::MAX
64///  - `grow()` must either return false or update `buf` and `cap` for a valid buffer
65///    of at least the requested buffer size.
66///  - If grow_failed is true all safety invariants on buf/cap/len MUST still hold.
67///  - `DiplomatWrite::flush()` will be automatically called by Diplomat. `flush()` might also be called
68///    (erroneously) on the Rust side (it's a public method), so it must be idempotent.
69#[repr(C)]
70pub struct DiplomatWrite {
71    /// Context pointer for additional data needed by `grow()` and `flush()`. May be `null`.
72    ///
73    /// The pointer may reference structured data on the foreign side,
74    /// such as C++ std::string, used to reallocate buf.
75    context: *mut c_void,
76    /// The raw string buffer, which will be mutated on the Rust side.
77    buf: *mut u8,
78    /// The current filled size of the buffer
79    len: usize,
80    /// The current capacity of the buffer
81    cap: usize,
82    /// Set to true if `grow` ever fails.
83    grow_failed: bool,
84    /// Called by Rust to indicate that there is no more data to write.
85    ///
86    /// May be called multiple times.
87    ///
88    /// Arguments:
89    /// - `self` (`*mut DiplomatWrite`): This `DiplomatWrite`
90    flush: extern "C" fn(*mut DiplomatWrite),
91    /// Called by Rust to request more capacity in the buffer. The implementation should allocate a new
92    /// buffer and copy the contents of the old buffer into the new buffer, updating `self.buf` and `self.cap`
93    ///
94    /// Arguments:
95    /// - `self` (`*mut DiplomatWrite`): This `DiplomatWrite`
96    /// - `capacity` (`usize`): The requested capacity.
97    ///
98    /// Returns: `true` if the allocation succeeded. Should not update any state if it failed.
99    grow: extern "C" fn(*mut DiplomatWrite, usize) -> bool,
100}
101
102impl DiplomatWrite {
103    /// Call this function before releasing the buffer to C
104    pub fn flush(&mut self) {
105        (self.flush)(self);
106    }
107
108    /// Returns a pointer to the buffer's bytes.
109    ///
110    /// If growth has failed, this returns what has been written so far.
111    pub fn as_bytes(&self) -> &[u8] {
112        if self.buf.is_null() {
113            return &[];
114        }
115        debug_assert!(self.len <= self.cap);
116        // Safety checklist, assuming this struct's safety invariants:
117        // 1. `buf` is a valid pointer and not null
118        // 2. `buf` points to `len` consecutive properly initialized bytes
119        // 3. `buf` won't be mutated because it is only directly accessible via
120        //    `diplomat_buffer_write_get_bytes`, whose safety invariant states
121        //    that the bytes cannot be mutated while borrowed
122        //    can only be dereferenced using unsafe code
123        // 4. `buf`'s total size is no larger than isize::MAX
124        unsafe { core::slice::from_raw_parts(self.buf, self.len) }
125    }
126}
127impl fmt::Write for DiplomatWrite {
128    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
129        if self.grow_failed {
130            return Ok(());
131        }
132        let needed_len = self.len + s.len();
133        if needed_len > self.cap {
134            let success = (self.grow)(self, needed_len);
135            if !success {
136                self.grow_failed = true;
137                return Ok(());
138            }
139        }
140        debug_assert!(needed_len <= self.cap);
141        unsafe {
142            ptr::copy_nonoverlapping(s.as_bytes().as_ptr(), self.buf.add(self.len), s.len());
143        }
144        self.len = needed_len;
145        Ok(())
146    }
147}
148
149/// Create an `DiplomatWrite` that can write to a fixed-length stack allocated `u8` buffer.
150///
151/// Once done, this will append a null terminator to the written string.
152///
153/// This is largely used internally by Diplomat-generated FFI code, and should not need to be constructed
154/// manually outside of that context. See [`RustWriteVec`](rust_interop::RustWriteVec) if you need this in Rust.
155///
156/// # Safety
157///
158///  - `buf` must be a valid pointer to a region of memory that can hold at `buf_size` bytes
159#[no_mangle]
160pub unsafe extern "C" fn diplomat_simple_write(buf: *mut u8, buf_size: usize) -> DiplomatWrite {
161    extern "C" fn grow(_this: *mut DiplomatWrite, _cap: usize) -> bool {
162        false
163    }
164    extern "C" fn flush(this: *mut DiplomatWrite) {
165        unsafe {
166            debug_assert!((*this).len <= (*this).cap);
167            let buf = (*this).buf;
168            ptr::write(buf.add((*this).len), 0)
169        }
170    }
171    DiplomatWrite {
172        context: ptr::null_mut(),
173        buf,
174        len: 0,
175        grow_failed: false,
176        // keep an extra byte in our pocket for the null terminator
177        cap: buf_size - 1,
178        flush,
179        grow,
180    }
181}
182
183/// Create an [`DiplomatWrite`] that can write to a dynamically allocated buffer managed by Rust.
184///
185/// Use [`diplomat_buffer_write_destroy()`] to free the writable and its underlying buffer.
186/// The pointer is valid until that function is called.
187///
188/// This is largely used internally by Diplomat-generated FFI code, and should not need to be constructed
189/// manually outside of that context. See [`RustWriteVec`](rust_interop::RustWriteVec) if you need this in Rust.
190///
191/// The grow impl never sets `grow_failed`, although it is possible for it to panic.
192#[no_mangle]
193pub extern "C" fn diplomat_buffer_write_create(cap: usize) -> *mut DiplomatWrite {
194    extern "C" fn grow(this: *mut DiplomatWrite, new_cap: usize) -> bool {
195        unsafe {
196            let this = this.as_mut().unwrap();
197            let mut vec = Vec::from_raw_parts(this.buf, 0, this.cap);
198            vec.reserve(new_cap);
199            this.cap = vec.capacity();
200            this.buf = vec.as_mut_ptr();
201            core::mem::forget(vec);
202        }
203        true
204    }
205
206    extern "C" fn flush(_: *mut DiplomatWrite) {}
207
208    let mut vec = Vec::<u8>::with_capacity(cap);
209    let ret = DiplomatWrite {
210        context: ptr::null_mut(),
211        buf: vec.as_mut_ptr(),
212        len: 0,
213        grow_failed: false,
214        cap,
215        flush,
216        grow,
217    };
218
219    core::mem::forget(vec);
220    Box::into_raw(Box::new(ret))
221}
222
223/// Grabs a pointer to the underlying buffer of a writable.
224///
225/// Returns null if there was an allocation error during the write construction.
226///
227/// # Safety
228/// - The returned pointer is valid until the passed writable is destroyed.
229/// - The returned pointer is valid for both reads and writes, however Rust code
230///   may not write to it if `this` is being accessed by other methods simultaneously.
231/// - `this` must be a pointer to a valid [`DiplomatWrite`] constructed by
232///   [`diplomat_buffer_write_create()`].
233#[no_mangle]
234pub extern "C" fn diplomat_buffer_write_get_bytes(this: *mut DiplomatWrite) -> *mut u8 {
235    let this = unsafe { &*this };
236    if this.grow_failed {
237        core::ptr::null_mut()
238    } else {
239        this.buf
240    }
241}
242
243/// Gets the length in bytes of the content written to the writable.
244///
245/// Returns 0 if there was an allocation error during the write construction.
246///
247/// # Safety
248/// - `this` must be a pointer to a valid [`DiplomatWrite`] constructed by
249///   [`diplomat_buffer_write_create()`].
250#[no_mangle]
251pub extern "C" fn diplomat_buffer_write_len(this: &DiplomatWrite) -> usize {
252    if this.grow_failed {
253        0
254    } else {
255        this.len
256    }
257}
258
259/// Destructor for Rust-memory backed writables.
260///
261/// # Safety
262/// - `this` must be a pointer to a valid [`DiplomatWrite`] constructed by
263///   [`diplomat_buffer_write_create()`].
264#[no_mangle]
265pub unsafe extern "C" fn diplomat_buffer_write_destroy(this: *mut DiplomatWrite) {
266    let this = Box::from_raw(this);
267    let vec = Vec::from_raw_parts(this.buf, 0, this.cap);
268    drop(vec);
269    drop(this);
270}