Skip to content

Commit fdbba9d

Browse files
0.2.6 (stable): NonDeDuplicatedStr
1 parent 7952112 commit fdbba9d

File tree

4 files changed

+117
-57
lines changed

4 files changed

+117
-57
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ This reflects GIT commits on `main` branch (the default branch), that is, `stabl
77
[`nightly` GIT branch](https://github.com/peter-lyons-kehl/ndd/tree/nightly) may occasionally be
88
behind `main`. See also [CONTRIBUTING.md](CONTRIBUTING.md).
99

10+
## 0.2.6 (stable) and 0.3.6-nightly
11+
12+
- Renamed `cross-crate-demo` -> `cross-crate-demo-problem`.
13+
- Moved `cross-crate-demo/bin*/*.sh` scripts one level deeper to `invocation_scripts`.
14+
- API
15+
- `NonDeDuplicatedStr`
16+
1017
## 0.2.5 (stable) and 0.3.5-nightly
1118

1219
- `NonDeDuplicated`'s generic parameter `T` must now implement

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ndd"
3-
version = "0.2.5"
3+
version = "0.2.6"
44
edition = "2024"
55

66
license = "BSD-2-Clause OR Apache-2.0 OR MIT"

src/lib.rs

Lines changed: 108 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33

44
use core::any::Any;
55
use core::cell::Cell;
6-
use core::ops::Deref;
6+
use core::marker::PhantomData;
77

88
/// A zero-cost wrapper guaranteed not to share its memory location with any other valid (in-scope)
99
/// variable (even `const` equal to the inner value). Use for `static` variables that have their
1010
/// addresses compared with [core::ptr::eq].
1111
///
12-
/// It has same size, layout and alignment as type parameter `T`.
12+
/// It has same size, layout and alignment as type parameter `OWN`.
1313
///
1414
/// `T` must implement [Any]. That is automatic for any types that don't have any lifetimes (other
15-
/// than `'static`). This requirement givesn an earlier error when `NonDeDuplicated` is used other
15+
/// than `'static`). This requirement gives an earlier error when `NonDeDuplicated` is used other
1616
/// than intended.
1717
///
1818
/// We don't just limit to be `:'static`, because then `NonDeDuplicated` could still be somewhat
@@ -37,17 +37,23 @@ use core::ops::Deref;
3737
/// But, by requiring `NonDeDuplicated`'s generic parameter `T` to implement [Any] the first
3838
/// example above fails, too. That prevents mistakes earlier.
3939
#[repr(transparent)]
40-
pub struct NonDeDuplicated<T: Any> {
41-
cell: Cell<T>,
40+
pub struct NonDeDuplicatedFlexible<FROM, OWN: Any + Send + Sync, TO: Any + ?Sized> {
41+
cell: Cell<OWN>,
42+
_f: PhantomData<FROM>,
43+
_t: PhantomData<TO>,
4244
}
4345

44-
impl<T: Any> NonDeDuplicated<T> {
46+
pub type NonDeDuplicated<T> = NonDeDuplicatedFlexible<T, T, T>;
47+
48+
impl<T: Any + Send + Sync> NonDeDuplicated<T> {
4549
/// Construct a new instance.
4650
pub const fn new(value: T) -> Self {
4751
Self {
4852
//Using core::hint::black_box() seems unnecessary.
49-
//cell: Cell::new(core::hint::black_box(data)),
53+
//cell: Cell::new(core::hint::black_box(value)),
5054
cell: Cell::new(value),
55+
_f: PhantomData,
56+
_t: PhantomData,
5157
}
5258
}
5359

@@ -58,37 +64,83 @@ impl<T: Any> NonDeDuplicated<T> {
5864
}
5965
}
6066

61-
impl<T: Any> Deref for NonDeDuplicated<T> {
62-
type Target = T;
63-
64-
fn deref(&self) -> &Self::Target {
65-
self.get()
67+
pub type NonDeDuplicatedStr<const N: usize> = NonDeDuplicatedFlexible<&'static str, [u8; N], str>;
68+
impl<const N: usize> NonDeDuplicatedStr<N> {
69+
/// Construct a new instance.
70+
pub const fn new(s: &str) -> Self {
71+
if s.len() > N {
72+
let msg = match s.len() - N {
73+
1 => "N is 1 byte too small.",
74+
2 => "N is 2 bytes too small.",
75+
3 => "N is 3 bytes too small.",
76+
4 => "N is 4 bytes too small.",
77+
_ => "N is more than 4 bytes too small.",
78+
};
79+
panic!("{}", msg)
80+
}
81+
if s.len() < N {
82+
let msg = match N - s.len() {
83+
1 => "N is 1 byte too large.",
84+
2 => "N is 2 bytes too large.",
85+
3 => "N is 3 bytes too large.",
86+
4 => "N is 4 bytes too large.",
87+
_ => "N is more than 4 bytes too large.",
88+
};
89+
panic!("{}", msg)
90+
}
91+
let bytes: &[u8] = s.as_bytes();
92+
let mut arr = [0u8; N];
93+
let mut i = 0;
94+
while i < bytes.len() {
95+
arr[i] = bytes[i];
96+
i += 1;
97+
}
98+
Self {
99+
cell: Cell::new(arr),
100+
_f: PhantomData,
101+
_t: PhantomData,
102+
}
66103
}
67-
}
68104

69-
impl<T: Any> From<T> for NonDeDuplicated<T> {
70-
fn from(value: T) -> Self {
71-
Self::new(value)
105+
/// Get a reference.
106+
///
107+
/// Implementation details: Since this type, and this function, is intended to be used for
108+
/// `static` or `const` variables, speed doesn't matter. So, we use [core::str::from_utf8]
109+
/// (instead of [core::str::from_utf8_unchecked]).
110+
pub const fn get(&self) -> &str {
111+
let ptr = self.cell.as_ptr();
112+
let bytes = unsafe { &*ptr };
113+
match core::str::from_utf8(bytes) {
114+
Ok(s) => s,
115+
Err(_) => unreachable!(),
116+
}
72117
}
73118
}
74119

75-
/// For now, [Sync] requires that `T` is both [Sync] AND [Send], following
120+
/// For now, [Sync] requires that `OWN` is both [Sync] AND [Send], following
76121
/// [std::sync::Mutex](https://doc.rust-lang.org/nightly/std/sync/struct.Mutex.html#impl-Sync-for-Mutex%3CT%3E).
77122
/// However, from <https://doc.rust-lang.org/nightly/core/marker/trait.Sync.html> it seems that `T:
78123
/// Send` may be unnecessary? Please advise.
79124
///
80125
/// Either way, [NonDeDuplicated] exists specifically for static variables. Those get never moved
81126
/// out. So, unlike [std::sync::Mutex], [NonDeDuplicated] itself doesn't need to implement [Send].
82-
unsafe impl<T: Any + Send + Sync> Sync for NonDeDuplicated<T> {}
127+
///
128+
/// Also, unclear if `TO` needs to be [Send] and [Sync].
129+
unsafe impl<FROM, OWN: Any + Send + Sync, TO: Any + ?Sized> Sync
130+
for NonDeDuplicatedFlexible<FROM, OWN, TO>
131+
{
132+
}
83133

84134
/// [NonDeDuplicated] is intended for `static` (immutable) variables only. So [Drop::drop] panics in
85-
/// debug builds.
86-
impl<T: Any> Drop for NonDeDuplicated<T> {
135+
/// debug/miri builds.
136+
impl<FROM, OWN: Any + Send + Sync, TO: Any + ?Sized> Drop
137+
for NonDeDuplicatedFlexible<FROM, OWN, TO>
138+
{
87139
fn drop(&mut self) {
88140
// If the client uses Box::leak() or friends, then drop() will NOT happen. That is OK: A
89141
// leaked reference will have static lifetime.
90142
#[cfg(any(debug_assertions, miri))]
91-
panic!("Do not use for local variables or on heap. Use for static variables only.")
143+
panic!("Do not use for local variables, const, or on heap. Use for static variables only.")
92144
}
93145
}
94146

@@ -103,16 +155,16 @@ mod tests {
103155
#[test]
104156
#[cfg(any(debug_assertions, miri))]
105157
#[should_panic(
106-
expected = "Do not use for local variables or on heap. Use for static variables only."
158+
expected = "Do not use for local variables, const, or on heap. Use for static variables only."
107159
)]
108160
fn drop_panics_in_debug_and_miri() {
109-
let _: NonDeDuplicated<()> = ().into();
161+
let _: NonDeDuplicated<()> = NonDeDuplicated::new(());
110162
}
111163

112164
#[cfg(not(any(debug_assertions, miri)))]
113165
#[test]
114166
fn drop_silent_in_release() {
115-
let _: NonDeDuplicated<()> = ().into();
167+
let _: NonDeDuplicated<()> = NonDeDuplicated::new(());
116168
}
117169

118170
const U8_CONST: u8 = b'A';
@@ -124,20 +176,6 @@ mod tests {
124176
assert!(!ptr::eq(&U8_STATIC_1, &U8_STATIC_2));
125177
}
126178

127-
fn _deref() -> &'static u8 {
128-
static N: NonDeDuplicated<u8> = NonDeDuplicated::<u8>::new(0);
129-
&N
130-
}
131-
132-
#[test]
133-
fn deref_of_copy_type() {
134-
static N: NonDeDuplicated<u8> = NonDeDuplicated::<u8>::new(0);
135-
136-
let deref = &*N;
137-
let get = N.get();
138-
assert!(ptr::eq(deref, get));
139-
}
140-
141179
#[cfg(not(any(debug_assertions, miri)))]
142180
/// In release, [U8_CONST] gets optimized away and points to the same address as
143181
/// [U8_STATIC_1]!
@@ -162,37 +200,44 @@ mod tests {
162200
assert!(!ptr::eq(U8_NDD_REF, &U8_STATIC_2));
163201
}
164202

165-
const STR_CONST_FROM_BYTE_ARRAY: &str = {
166-
if let Ok(s) = str::from_utf8(&[b'H', b'i']) {
167-
s
168-
} else {
169-
panic!()
170-
}
171-
};
172-
const STR_CONST_FROM_BYTE_STRING: &str = {
173-
if let Ok(s) = str::from_utf8(b"Hello") {
174-
s
175-
} else {
176-
panic!()
203+
const STR_CONST_FROM_BYTE_ARRAY_HI: &str = {
204+
match str::from_utf8(&[b'H', b'i']) {
205+
Ok(s) => s,
206+
Err(_) => unreachable!(),
177207
}
178208
};
179209

180210
#[cfg(not(miri))]
181211
#[test]
182-
#[should_panic(expected = "assertion failed: !ptr::eq(STR_CONST_FROM_BYTE_ARRAY, \"Hi\")")]
212+
#[should_panic(expected = "assertion failed: !ptr::eq(STR_CONST_FROM_BYTE_ARRAY_HI, \"Hi\")")]
183213
fn str_global_byte_slice_const_and_local_str_release_and_debug() {
184-
assert!(!ptr::eq(STR_CONST_FROM_BYTE_ARRAY, "Hi"));
214+
assert!(!ptr::eq(STR_CONST_FROM_BYTE_ARRAY_HI, "Hi"));
185215
}
186216
#[cfg(miri)]
187217
#[test]
188218
fn str_global_byte_slice_const_and_local_str_miri() {
189-
assert!(!ptr::eq(STR_CONST_FROM_BYTE_ARRAY, "Hi"));
219+
assert!(!ptr::eq(STR_CONST_FROM_BYTE_ARRAY_HI, "Hi"));
190220
}
191221

222+
const STR_CONST_FROM_BYTE_STRING_HELLO: &str = {
223+
match str::from_utf8(b"Hello") {
224+
Ok(s) => s,
225+
Err(_) => unreachable!(),
226+
}
227+
};
228+
192229
/// This is the same for all three: release, debug AND miri!
193230
#[test]
194-
fn str_global_byte_by_byte_const_and_local_static_miri() {
195-
assert!(ptr::eq(STR_CONST_FROM_BYTE_STRING, "Hello"));
231+
fn str_global_byte_by_byte_const_and_local_static() {
232+
assert!(ptr::eq(STR_CONST_FROM_BYTE_STRING_HELLO, "Hello"));
233+
}
234+
235+
static STR_NDD_HI: NonDeDuplicatedStr<5> = NonDeDuplicatedStr::new("Hello");
236+
#[test]
237+
fn str_ndd_hi() {
238+
assert!(!ptr::eq(STR_NDD_HI.get(), "Hi"));
239+
assert!(!ptr::eq(STR_NDD_HI.get(), STR_CONST_FROM_BYTE_ARRAY_HI));
240+
assert!(!ptr::eq(STR_NDD_HI.get(), STR_CONST_FROM_BYTE_STRING_HELLO));
196241
}
197242

198243
static STR_STATIC: &str = "Ciao";
@@ -214,6 +259,14 @@ mod tests {
214259
assert!(!ptr::eq(local_const_based_slice, STR_STATIC));
215260
}
216261

262+
static STR_NDD_CIAO: NonDeDuplicatedStr<4> = NonDeDuplicatedStr::new("Ciao");
263+
#[test]
264+
fn str_local_const_based_and_str_ndd() {
265+
const LOCAL_CONST_ARR: [u8; 4] = [b'C', b'i', b'a', b'o'];
266+
let local_const_based_slice: &str = str::from_utf8(&LOCAL_CONST_ARR).unwrap();
267+
assert!(!ptr::eq(local_const_based_slice, STR_NDD_CIAO.get()));
268+
}
269+
217270
mod cross_module_static {
218271
pub static STATIC_OPT_U8_A: Option<u8> = Some(b'A');
219272
}

0 commit comments

Comments
 (0)