Skip to main content

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}