Skip to content

Commit db11076

Browse files
committed
add nvim_oxi::string! macro that produces nvim_oxi::Strings
types: rename `size` field to `len` in `String` types: remove `#[repr(C)]` from `StringBuilder` types: use the `String` type itself instead of redefining the fields in `StringBuilder` types: a less conservative reallocation for `StringBuilder` types: update comment on deallocation in `StringBuilder` types(string): keep the size of allocation in string to a power of 2.
1 parent 95bfc3c commit db11076

File tree

4 files changed

+149
-21
lines changed

4 files changed

+149
-21
lines changed

crates/types/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod dictionary;
99
mod error;
1010
mod function;
1111
mod kvec;
12+
mod macros;
1213
mod non_owning;
1314
mod object;
1415
#[cfg(feature = "serde")]
@@ -22,7 +23,7 @@ pub use error::Error;
2223
pub use function::Function;
2324
pub use non_owning::NonOwning;
2425
pub use object::{Object, ObjectKind};
25-
pub use string::String;
26+
pub use string::{String, StringBuilder};
2627

2728
pub mod iter {
2829
//! Iterators over [`Array`](crate::Array)s and

crates/types/src/macros.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// Same as [`format!`] but creates an [`nvim_oxi::String`](crate::String).
2+
#[macro_export]
3+
macro_rules! string {
4+
($($tt:tt)*) => {{
5+
let mut w = $crate::StringBuilder::new();
6+
::core::fmt::Write::write_fmt(&mut w, format_args!($($tt)*))
7+
.expect("a formatting trait implementation returned an error");
8+
w.finish()
9+
}};
10+
}

crates/types/src/string.rs

Lines changed: 136 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use alloc::borrow::Cow;
44
use alloc::string::String as StdString;
5-
use core::{ffi, slice};
5+
use core::{ffi, fmt, ptr, slice};
66
use std::path::{Path, PathBuf};
77

88
use luajit as lua;
@@ -19,7 +19,15 @@ use crate::NonOwning;
1919
#[repr(C)]
2020
pub struct String {
2121
pub(super) data: *mut ffi::c_char,
22-
pub(super) size: usize,
22+
pub(super) len: usize,
23+
}
24+
25+
/// A builder that can be used to efficiently build a [`nvim_oxi::String`](String).
26+
pub struct StringBuilder {
27+
/// The underlying string being constructed.
28+
pub(super) inner: String,
29+
/// Current capacity (i.e., allocated memory) of this builder in bytes.
30+
pub(super) cap: usize,
2331
}
2432

2533
impl Default for String {
@@ -43,14 +51,28 @@ impl core::fmt::Display for String {
4351
}
4452
}
4553

54+
impl Default for StringBuilder {
55+
#[inline]
56+
fn default() -> Self {
57+
Self::new()
58+
}
59+
}
60+
61+
impl fmt::Write for StringBuilder {
62+
fn write_str(&mut self, s: &str) -> fmt::Result {
63+
self.push_bytes(s.as_bytes());
64+
Ok(())
65+
}
66+
}
67+
4668
impl String {
4769
#[inline]
4870
pub fn as_bytes(&self) -> &[u8] {
4971
if self.data.is_null() {
5072
&[]
5173
} else {
5274
assert!(self.len() <= isize::MAX as usize);
53-
unsafe { slice::from_raw_parts(self.data as *const u8, self.size) }
75+
unsafe { slice::from_raw_parts(self.data as *const u8, self.len) }
5476
}
5577
}
5678

@@ -65,20 +87,9 @@ impl String {
6587
/// by a null byte.
6688
#[inline]
6789
pub fn from_bytes(bytes: &[u8]) -> Self {
68-
let data =
69-
unsafe { libc::malloc(bytes.len() + 1) as *mut ffi::c_char };
70-
71-
unsafe {
72-
libc::memcpy(
73-
data as *mut _,
74-
bytes.as_ptr() as *const _,
75-
bytes.len(),
76-
)
77-
};
78-
79-
unsafe { *data.add(bytes.len()) = 0 };
80-
81-
Self { data: data as *mut _, size: bytes.len() }
90+
let mut s = StringBuilder::new();
91+
s.push_bytes(bytes);
92+
s.finish()
8293
}
8394

8495
/// Returns `true` if the `String` has a length of zero.
@@ -90,13 +101,13 @@ impl String {
90101
/// Returns the length of the `String`, *not* including the final null byte.
91102
#[inline]
92103
pub fn len(&self) -> usize {
93-
self.size
104+
self.len
94105
}
95106

96107
/// Creates a new, empty `String`.
97108
#[inline]
98109
pub fn new() -> Self {
99-
Self { data: core::ptr::null_mut(), size: 0 }
110+
Self { data: ptr::null_mut(), len: 0 }
100111
}
101112

102113
/// Makes a non-owning version of this `String`.
@@ -115,6 +126,86 @@ impl String {
115126
}
116127
}
117128

129+
impl StringBuilder {
130+
/// Create a new empty `StringBuilder`.
131+
#[inline]
132+
pub fn new() -> Self {
133+
Self { inner: String::new(), cap: 0 }
134+
}
135+
136+
/// Push new bytes to the builder.
137+
#[inline]
138+
pub fn push_bytes(&mut self, bytes: &[u8]) {
139+
if self.inner.data.is_null() {
140+
let len = bytes.len();
141+
let cap = len + 1;
142+
143+
let data = unsafe {
144+
let data = libc::malloc(cap) as *mut ffi::c_char;
145+
146+
libc::memcpy(data as *mut _, bytes.as_ptr() as *const _, len);
147+
148+
*data.add(len) = 0;
149+
150+
data
151+
};
152+
153+
self.inner.data = data;
154+
self.inner.len = len;
155+
self.cap = cap;
156+
157+
return;
158+
}
159+
160+
let slice_len = bytes.len();
161+
let required_cap = self.inner.len + slice_len + 1;
162+
163+
// Reallocate if pushing the bytes overflows the allocated memory.
164+
if self.cap < required_cap {
165+
// The smallest number `n`, such that `required_cap <= * 2^n`.
166+
let n = (required_cap - 1).ilog2() + 1;
167+
let new_cap = 2_usize.pow(n).max(4);
168+
169+
self.inner.data = unsafe {
170+
libc::realloc(self.inner.data as *mut _, new_cap)
171+
as *mut ffi::c_char
172+
};
173+
174+
self.cap = new_cap;
175+
debug_assert!(self.inner.len < self.cap)
176+
}
177+
178+
// Pushing the `bytes` is safe now.
179+
let new_len = unsafe {
180+
libc::memcpy(
181+
self.inner.data.add(self.inner.len) as *mut _,
182+
bytes.as_ptr() as *const _,
183+
slice_len,
184+
);
185+
186+
let new_len = self.inner.len + slice_len;
187+
188+
*self.inner.data.add(new_len) = 0;
189+
190+
new_len
191+
};
192+
193+
self.inner.len = new_len;
194+
debug_assert!(self.inner.len < self.cap);
195+
}
196+
197+
/// Build the `String`.
198+
#[inline]
199+
pub fn finish(self) -> String {
200+
let s = String { data: self.inner.data, len: self.inner.len };
201+
202+
// Prevent self's destructor from being called.
203+
std::mem::forget(self);
204+
205+
s
206+
}
207+
}
208+
118209
impl Clone for String {
119210
#[inline]
120211
fn clone(&self) -> Self {
@@ -132,6 +223,14 @@ impl Drop for String {
132223
}
133224
}
134225

226+
impl Drop for StringBuilder {
227+
fn drop(&mut self) {
228+
if !self.inner.data.is_null() {
229+
unsafe { libc::free(self.inner.data as *mut _) }
230+
}
231+
}
232+
}
233+
135234
impl From<&str> for String {
136235
#[inline]
137236
fn from(s: &str) -> Self {
@@ -229,7 +328,7 @@ impl PartialEq<std::string::String> for String {
229328
impl core::hash::Hash for String {
230329
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
231330
self.as_bytes().hash(state);
232-
self.size.hash(state);
331+
self.len.hash(state);
233332
}
234333
}
235334

@@ -359,4 +458,21 @@ mod tests {
359458

360459
assert_eq!(lhs, rhs);
361460
}
461+
462+
#[test]
463+
fn builder() {
464+
let str = "foo bar";
465+
let bytes = b"baz foo bar";
466+
467+
let mut s = StringBuilder::new();
468+
s.push_bytes(str.as_bytes());
469+
s.push_bytes(bytes);
470+
471+
assert_eq!(s.inner.len, str.len() + bytes.len());
472+
assert_eq!(s.cap, 32); // Allocation size
473+
assert_eq!(unsafe { *s.inner.data.add(s.inner.len) }, 0); // Null termination
474+
475+
let s = s.finish();
476+
assert_eq!(s.len(), str.len() + bytes.len());
477+
}
362478
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,4 @@ pub mod tests;
9696
#[cfg_attr(docsrs, doc(cfg(feature = "test-terminator")))]
9797
pub use tests::{TestFailure, TestTerminator};
9898
pub use toplevel::*;
99+
pub use types::string;

0 commit comments

Comments
 (0)