jacquard_common/bos.rs
1//! Borrow-or-share traits for abstracting over owned and borrowed string representations.
2//!
3//! This module is a vendored copy of the [`borrow-or-share`](https://docs.rs/borrow-or-share/0.2.4/)
4//! crate by yescallop, with additional implementations for [`SmolStr`](smol_str::SmolStr)
5//! and [`CowStr`](crate::CowStr). We vendor rather than depend on the crate to avoid
6//! orphan rule issues. We need to implement `Bos<str>` for `SmolStr` and `CowStr`, which
7//! are foreign types relative to the upstream crate.
8//!
9//! # Overview
10//!
11//! [`Bos<T>`] is the base trait providing a GAT for the reference type. Use it as a bound
12//! when you need a method that borrows from `*self` regardless of whether the backing type
13//! is owned or borrowed:
14//!
15//! ```ignore
16//! impl<T: Bos<str>> AsRef<str> for MyType<T> {
17//! fn as_ref(&self) -> &str {
18//! self.as_str()
19//! }
20//! }
21//! ```
22//!
23//! [`BorrowOrShare<'i, 'o, T>`] is the convenience trait with split lifetimes. Use it when
24//! you want a method on `&'i self` that returns `&'o T`, where `'o` may outlive `'i` when
25//! the backing type is a reference:
26//!
27//! ```ignore
28//! impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> MyType<T> {
29//! fn as_str(&'i self) -> &'o str {
30//! self.0.borrow_or_share()
31//! }
32//! }
33//! ```
34
35use alloc::{
36 borrow::{Cow, ToOwned},
37 boxed::Box,
38 string::String,
39 vec::Vec,
40};
41
42use smol_str::SmolStr;
43
44use crate::CowStr;
45
46mod internal {
47 pub trait Ref<T: ?Sized> {
48 fn cast<'a>(self) -> &'a T
49 where
50 Self: 'a;
51 }
52
53 impl<T: ?Sized> Ref<T> for &T {
54 #[inline]
55 fn cast<'a>(self) -> &'a T
56 where
57 Self: 'a,
58 {
59 self
60 }
61 }
62}
63
64use internal::Ref;
65
66/// A trait for either borrowing or sharing data.
67///
68/// See the [module-level documentation](self) for more details.
69pub trait Bos<T: ?Sized> {
70 /// The resulting reference type. May only be `&T`.
71 type Ref<'this>: Ref<T>
72 where
73 Self: 'this;
74
75 /// Borrows from `*this` or from behind a reference it holds,
76 /// returning a reference of type [`Self::Ref`].
77 ///
78 /// In the latter case, the returned reference is said to be *shared* with `*this`.
79 fn borrow_or_share(this: &Self) -> Self::Ref<'_>;
80}
81
82/// A helper trait for writing "data borrowing or sharing" functions.
83///
84/// See the [module-level documentation](self) for more details.
85pub trait BorrowOrShare<'i, 'o, T: ?Sized>: Bos<T> {
86 /// Borrows from `*self` or from behind a reference it holds.
87 ///
88 /// In the latter case, the returned reference is said to be *shared* with `*self`.
89 fn borrow_or_share(&'i self) -> &'o T;
90}
91
92impl<'i, 'o, T: ?Sized, B> BorrowOrShare<'i, 'o, T> for B
93where
94 B: Bos<T> + ?Sized + 'i,
95 B::Ref<'i>: 'o,
96{
97 #[inline]
98 fn borrow_or_share(&'i self) -> &'o T {
99 (B::borrow_or_share(self) as B::Ref<'i>).cast()
100 }
101}
102
103// --- Reference impl (sharing) ---
104
105impl<'a, T: ?Sized> Bos<T> for &'a T {
106 type Ref<'this>
107 = &'a T
108 where
109 Self: 'this;
110
111 #[inline]
112 fn borrow_or_share(this: &Self) -> Self::Ref<'_> {
113 this
114 }
115}
116
117// --- Macro for borrowing impls ---
118
119/// Implement [`Bos`] for types that always borrow from `*self`.
120///
121/// Each entry maps a concrete type to the target slice/str type it derefs to.
122/// The generated impl uses `Ref<'this> = &'this $target` — pure borrowing, no sharing.
123#[macro_export]
124macro_rules! impl_bos {
125 ($($(#[$attr:meta])? $({$($params:tt)*})? $ty:ty => $target:ty)*) => {
126 $(
127 $(#[$attr])?
128 impl $(<$($params)*>)? $crate::bos::Bos<$target> for $ty {
129 type Ref<'this> = &'this $target where Self: 'this;
130
131 #[inline]
132 fn borrow_or_share(this: &Self) -> Self::Ref<'_> {
133 this
134 }
135 }
136 )*
137 };
138}
139
140// --- Standard library impls ---
141
142impl_bos! {
143 //{T: ?Sized} T => T
144
145 {T: ?Sized} &mut T => T
146
147 {T, const N: usize} [T; N] => [T]
148
149 {T} Vec<T> => [T]
150
151 String => str
152
153 {T: ?Sized} Box<T> => T
154 {B: ?Sized + ToOwned} Cow<'_, B> => B
155
156 {T: ?Sized} alloc::sync::Arc<T> => T
157 {T: ?Sized} alloc::rc::Rc<T> => T
158}
159
160#[cfg(feature = "std")]
161impl_bos! {
162 std::ffi::OsString => std::ffi::OsStr
163 std::path::PathBuf => std::path::Path
164 alloc::ffi::CString => core::ffi::CStr
165}
166
167// --- SmolStr impl ---
168
169impl_bos! {
170 SmolStr => str
171}
172
173// --- CowStr impl ---
174
175impl<'a> Bos<str> for CowStr<'a> {
176 type Ref<'this>
177 = &'this str
178 where
179 Self: 'this;
180
181 #[inline]
182 fn borrow_or_share(this: &Self) -> Self::Ref<'_> {
183 this.as_str()
184 }
185}
186
187/// The default string backing type for jacquard's type-parameterised types.
188///
189/// `SmolStr` is used as the default because it satisfies `DeserializeOwned` (no lifetime
190/// annotation required) and provides small-string inline storage without heap allocation
191/// for strings of 22 bytes or fewer.
192pub type DefaultStr = SmolStr;
193
194/// Construct a value from a static string literal without allocation where possible.
195///
196/// For `SmolStr`, this uses `SmolStr::new_static()` which stores a pointer to the
197/// static string in the binary — zero allocation. For `CowStr`, it uses
198/// `CowStr::new_static()`. For `String` and `&'static str`, it does the obvious thing.
199pub trait FromStaticStr {
200 /// Construct from a `&'static str` without allocation where possible.
201 fn from_static(s: &'static str) -> Self;
202}
203
204impl FromStaticStr for SmolStr {
205 #[inline]
206 fn from_static(s: &'static str) -> Self {
207 SmolStr::new_static(s)
208 }
209}
210
211impl FromStaticStr for String {
212 #[inline]
213 fn from_static(s: &'static str) -> Self {
214 String::from(s)
215 }
216}
217
218impl<'a> FromStaticStr for CowStr<'a> {
219 #[inline]
220 fn from_static(s: &'static str) -> Self {
221 CowStr::new_static(s)
222 }
223}
224
225impl<'a> FromStaticStr for Cow<'a, str> {
226 #[inline]
227 fn from_static(s: &'static str) -> Self {
228 Cow::Borrowed(s)
229 }
230}
231
232impl<'a> FromStaticStr for &'a str {
233 #[inline]
234 fn from_static(s: &'a str) -> Self {
235 s
236 }
237}
238
239/// Combined trait bound for AT Protocol string backing types.
240///
241/// This is the universal bound for generic string-parameterised types in jacquard.
242/// Instead of writing `S: Bos<str> + AsRef<str>` everywhere, use `S: BosStr`.
243///
244/// Includes:
245/// - [`Bos<str>`] — borrow-or-share semantics
246/// - [`AsRef<str>`] — string slice access
247/// - [`FromStaticStr`] — zero-alloc construction from string literals
248pub trait BosStr: Bos<str> + AsRef<str> + FromStaticStr {}
249
250impl<T> BosStr for T where T: Bos<str> + AsRef<str> + FromStaticStr {}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 // Verify BorrowOrShare works for all backing types.
257
258 fn as_str_via_bos<'i, 'o, S: BorrowOrShare<'i, 'o, str>>(s: &'i S) -> &'o str {
259 s.borrow_or_share()
260 }
261
262 #[test]
263 fn bos_smolstr() {
264 let s = SmolStr::new("hello");
265 assert_eq!(as_str_via_bos(&s), "hello");
266 }
267
268 #[test]
269 fn bos_string() {
270 let s = String::from("hello");
271 assert_eq!(as_str_via_bos(&s), "hello");
272 }
273
274 #[test]
275 fn bos_ref_str() {
276 let s: &str = "hello";
277 assert_eq!(as_str_via_bos(&s), "hello");
278 }
279
280 #[test]
281 fn bos_cowstr_borrowed() {
282 let s = CowStr::Borrowed("hello");
283 assert_eq!(as_str_via_bos(&s), "hello");
284 }
285
286 #[test]
287 fn bos_cowstr_owned() {
288 let s = CowStr::Owned(SmolStr::new("hello"));
289 assert_eq!(as_str_via_bos(&s), "hello");
290 }
291
292 // Verify Bos (non-sharing) works via AsRef-style usage.
293
294 fn as_ref_via_bos<S: Bos<str>>(s: &S) -> &str {
295 let r = S::borrow_or_share(s);
296 r.cast()
297 }
298
299 #[test]
300 fn bos_as_ref_smolstr() {
301 let s = SmolStr::new("world");
302 assert_eq!(as_ref_via_bos(&s), "world");
303 }
304
305 #[test]
306 fn bos_as_ref_ref_str() {
307 let s: &str = "world";
308 assert_eq!(as_ref_via_bos(&s), "world");
309 }
310
311 // Verify sharing semantics: &str reference outlives the wrapper.
312
313 #[test]
314 fn ref_str_sharing_outlives_wrapper() {
315 let original: &str = "shared";
316 let result: &str;
317 {
318 let wrapper: &&str = &original;
319 result = as_str_via_bos(wrapper);
320 }
321 // result outlives wrapper because &str shares, not borrows.
322 assert_eq!(result, "shared");
323 }
324}