Skip to content

Commit a9fe832

Browse files
committed
[workerd-cxx] add kj::Maybe to kj-rs and workerd-cxx
1 parent 440afe9 commit a9fe832

25 files changed

+1040
-12
lines changed

gen/src/write.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
233233
Type::SliceRef(_) => out.builtin.rust_slice = true,
234234
Type::Array(_) => out.include.array = true,
235235
Type::Ref(_) | Type::Void(_) | Type::Ptr(_) => {}
236-
Type::Future(_) | Type::Own(_) => out.include.kj_rs = true,
236+
Type::Future(_) | Type::Maybe(_) | Type::Own(_) => out.include.kj_rs = true,
237237
}
238238
}
239239
}
@@ -1263,6 +1263,11 @@ fn write_type(out: &mut OutFile, ty: &Type) {
12631263
write_type(out, &ptr.inner);
12641264
write!(out, ">");
12651265
}
1266+
Type::Maybe(ptr) => {
1267+
write!(out, "::kj::Maybe<");
1268+
write_type(out, &ptr.inner);
1269+
write!(out, ">");
1270+
}
12661271
Type::CxxVector(ty) => {
12671272
write!(out, "::std::vector<");
12681273
write_type(out, &ty.inner);
@@ -1357,6 +1362,7 @@ fn write_space_after_type(out: &mut OutFile, ty: &Type) {
13571362
| Type::SharedPtr(_)
13581363
| Type::WeakPtr(_)
13591364
| Type::Str(_)
1365+
| Type::Maybe(_)
13601366
| Type::CxxVector(_)
13611367
| Type::RustVec(_)
13621368
| Type::SliceRef(_)
@@ -1431,6 +1437,7 @@ fn write_generic_instantiations(out: &mut OutFile) {
14311437
ImplKey::RustVec(ident) => write_rust_vec_extern(out, ident),
14321438
ImplKey::UniquePtr(ident) => write_unique_ptr(out, ident),
14331439
ImplKey::Own(ident) => write_kj_own(out, ident),
1440+
ImplKey::Maybe(ident) => write_kj_maybe(out, ident),
14341441
ImplKey::SharedPtr(ident) => write_shared_ptr(out, ident),
14351442
ImplKey::WeakPtr(ident) => write_weak_ptr(out, ident),
14361443
ImplKey::CxxVector(ident) => write_cxx_vector(out, ident),
@@ -1669,6 +1676,17 @@ fn write_kj_own(out: &mut OutFile, key: NamedImplKey) {
16691676
);
16701677
}
16711678

1679+
// Writes static assertions for Maybe
1680+
fn write_kj_maybe(out: &mut OutFile, key: NamedImplKey) {
1681+
let ident = key.rust;
1682+
let resolve = out.types.resolve(ident);
1683+
let inner = resolve.name.to_fully_qualified();
1684+
1685+
out.include.utility = true;
1686+
out.include.kj_rs = true;
1687+
writeln!(out, "static_assert(!::std::is_pointer<{}>::value, \"Maybe<T*> is not supported in workerd-cxx\");", inner);
1688+
}
1689+
16721690
fn write_unique_ptr(out: &mut OutFile, key: NamedImplKey) {
16731691
let ty = UniquePtr::Ident(key.rust);
16741692
write_unique_ptr_common(out, ty);

kj-rs/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use awaiter::WakerRef;
44
pub use crate::ffi::KjWaker;
55
pub use awaiter::PromiseAwaiter;
66
pub use future::FuturePollStatus;
7+
pub use maybe::repr::Maybe;
78
pub use own::repr::Own;
89
pub use promise::KjPromise;
910
pub use promise::KjPromiseNodeImpl;
@@ -13,12 +14,14 @@ pub use promise::new_callbacks_promise_future;
1314

1415
mod awaiter;
1516
mod future;
17+
pub mod maybe;
1618
mod own;
1719
mod promise;
1820
mod waker;
1921

2022
pub mod repr {
2123
pub use crate::future::repr::*;
24+
pub use crate::maybe::repr::*;
2225
pub use crate::own::repr::*;
2326
}
2427

kj-rs/maybe.rs

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
use repr::Maybe;
2+
use std::mem::MaybeUninit;
3+
4+
/// # Safety
5+
/// This trait should only be implemented in `workerd-cxx` on types
6+
/// which contain a specialization of `kj::Maybe` that needs to be represented in
7+
/// Rust.
8+
unsafe trait HasNiche: Sized {
9+
fn is_niche(value: &MaybeUninit<Self>) -> bool;
10+
}
11+
12+
// In Rust, references are not allowed to be null, so a null `MaybeUninit<&T>` is a niche
13+
unsafe impl<T> HasNiche for &T {
14+
fn is_niche(value: &MaybeUninit<&T>) -> bool {
15+
unsafe {
16+
// This is to avoid potentially zero-initalizing a reference, which is always undefined behavior
17+
std::mem::transmute_copy::<MaybeUninit<&T>, MaybeUninit<*const T>>(value)
18+
.assume_init()
19+
.is_null()
20+
}
21+
}
22+
}
23+
24+
unsafe impl<T> HasNiche for &mut T {
25+
fn is_niche(value: &MaybeUninit<&mut T>) -> bool {
26+
unsafe {
27+
// This is to avoid potentially zero-initalizing a reference, which is always undefined behavior
28+
std::mem::transmute_copy::<MaybeUninit<&mut T>, MaybeUninit<*mut T>>(value)
29+
.assume_init()
30+
.is_null()
31+
}
32+
}
33+
}
34+
35+
// In `kj`, `kj::Own<T>` are considered `none` in a `Maybe` if the pointer is null
36+
unsafe impl<T> HasNiche for crate::repr::Own<T> {
37+
fn is_niche(value: &MaybeUninit<crate::repr::Own<T>>) -> bool {
38+
unsafe { value.assume_init_ref().as_ptr().is_null() }
39+
}
40+
}
41+
42+
/// Trait that is used as the bounds for what can be in a Maybe
43+
///
44+
/// # Safety
45+
/// This trait should only be implemented from macro expansion and should
46+
/// never be manually implemented.
47+
pub unsafe trait MaybeItem: Sized {
48+
type Discriminant: Copy;
49+
const NONE: Maybe<Self>;
50+
fn is_some(value: &Maybe<Self>) -> bool;
51+
fn is_none(value: &Maybe<Self>) -> bool;
52+
fn some(value: Self) -> Maybe<Self>;
53+
fn from_option(value: Option<Self>) -> Maybe<Self> {
54+
match value {
55+
None => Self::NONE,
56+
Some(val) => Self::some(val),
57+
}
58+
}
59+
fn drop_in_place(value: &mut Maybe<Self>) {
60+
if <Self as MaybeItem>::is_some(value) {
61+
unsafe {
62+
value.some.assume_init_drop();
63+
}
64+
}
65+
}
66+
}
67+
68+
/// Macro to implement [`MaybeItem`] for `T` which implment [`HasNiche`]
69+
macro_rules! impl_maybe_item_for_has_niche {
70+
($ty:ty) => {
71+
unsafe impl<T> MaybeItem for $ty {
72+
type Discriminant = ();
73+
74+
fn is_some(value: &Maybe<Self>) -> bool {
75+
!<$ty as HasNiche>::is_niche(&value.some)
76+
}
77+
78+
fn is_none(value: &Maybe<Self>) -> bool {
79+
<$ty as HasNiche>::is_niche(&value.some)
80+
}
81+
82+
const NONE: Maybe<Self> = {
83+
Maybe {
84+
is_set: (),
85+
some: MaybeUninit::zeroed(),
86+
}
87+
};
88+
89+
fn some(value: Self) -> Maybe<Self> {
90+
Maybe {
91+
is_set: (),
92+
some: MaybeUninit::new(value)
93+
}
94+
}
95+
}
96+
};
97+
($ty:ty, $($tail:ty),+) => {
98+
impl_maybe_item_for_has_niche!($ty);
99+
impl_maybe_item_for_has_niche!($($tail),*);
100+
};
101+
}
102+
103+
/// Macro to implement [`MaybeItem`] for primitives
104+
macro_rules! impl_maybe_item_for_primitive {
105+
($ty:ty) => {
106+
unsafe impl MaybeItem for $ty {
107+
type Discriminant = bool;
108+
109+
fn is_some(value: &Maybe<Self>) -> bool {
110+
value.is_set
111+
}
112+
113+
fn is_none(value: &Maybe<Self>) -> bool {
114+
!value.is_set
115+
}
116+
117+
const NONE: Maybe<Self> = {
118+
Maybe {
119+
is_set: false,
120+
some: MaybeUninit::uninit(),
121+
}
122+
};
123+
124+
fn some(value: Self) -> Maybe<Self> {
125+
Maybe {
126+
is_set: true,
127+
some: MaybeUninit::new(value)
128+
}
129+
}
130+
}
131+
};
132+
($ty:ty, $($tail:ty),+) => {
133+
impl_maybe_item_for_primitive!($ty);
134+
impl_maybe_item_for_primitive!($($tail),*);
135+
};
136+
}
137+
138+
impl_maybe_item_for_has_niche!(crate::Own<T>, &T, &mut T);
139+
impl_maybe_item_for_primitive!(
140+
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, bool
141+
);
142+
143+
pub(crate) mod repr {
144+
use super::MaybeItem;
145+
use static_assertions::assert_eq_size;
146+
use std::fmt::{Debug, Display};
147+
use std::mem::MaybeUninit;
148+
149+
/// A [`Maybe`] represents bindings to the `kj::Maybe` class.
150+
/// It is an optional type, but represented using a struct, for alignment with kj.
151+
///
152+
/// # Layout
153+
/// In kj, `Maybe` has 3 specializations, one without niche value optimization, and
154+
/// two with it. In order to maintain an identical layout in Rust, we include an associated type
155+
/// in the [`MaybeItem`] trait, which determines the discriminant of the `Maybe<T: MaybeItem>`.
156+
///
157+
/// ## Niche Value Optimization
158+
/// This discriminant is used in tandem with the [`crate::maybe::HasNiche`] to implement
159+
/// [`MaybeItem`] properly for values which have a niche, which use a discriminant of [`()`],
160+
/// the unit type. All other types use [`bool`].
161+
#[repr(C)]
162+
pub struct Maybe<T: MaybeItem> {
163+
pub(super) is_set: T::Discriminant,
164+
pub(super) some: MaybeUninit<T>,
165+
}
166+
167+
assert_eq_size!(Maybe<isize>, [usize; 2]);
168+
assert_eq_size!(Maybe<&isize>, usize);
169+
assert_eq_size!(Maybe<crate::Own<isize>>, [usize; 2]);
170+
171+
impl<T: MaybeItem> Maybe<T> {
172+
/// # Safety
173+
/// This function shouldn't be used except by macro generation.
174+
pub unsafe fn is_set(&self) -> T::Discriminant {
175+
self.is_set
176+
}
177+
178+
/// # Safety
179+
/// This function shouldn't be used except by macro generation.
180+
pub const unsafe fn from_parts_unchecked(
181+
is_set: T::Discriminant,
182+
some: MaybeUninit<T>,
183+
) -> Maybe<T> {
184+
Maybe { is_set, some }
185+
}
186+
187+
pub fn is_some(&self) -> bool {
188+
T::is_some(self)
189+
}
190+
191+
pub fn is_none(&self) -> bool {
192+
T::is_none(self)
193+
}
194+
195+
// # CONSTRUCTORS
196+
// These emulate Rust's enum api, which offers constructors for each variant.
197+
// This mean matching cases, syntax, and behavior.
198+
// The only place this may be an issue is pattern matching, which will not work,
199+
// but should produce an error.
200+
//
201+
// The following fails to compile:
202+
// ```rust,compile_fail
203+
// match maybe {
204+
// Maybe::Some(_) => ...,
205+
// Maybe::None => ...,
206+
// }
207+
// ```
208+
209+
/// The [`Maybe::Some`] function serves the same purpose as an enum constructor.
210+
///
211+
/// Constructing a `Maybe<T>::Some(val)` should only be possible with a valid
212+
/// instance of `T` from Rust.
213+
#[allow(non_snake_case)]
214+
pub fn Some(value: T) -> Maybe<T> {
215+
T::some(value)
216+
}
217+
218+
/// The [`Maybe::None`] functions as a constructor for the none variant. It uses
219+
/// a `const` instead of a function to match syntax with normal Rust enums.
220+
///
221+
/// Constructing a `Maybe<T>::None` variant should always be possible from Rust.
222+
#[allow(non_upper_case_globals, dead_code)]
223+
pub const None: Maybe<T> = T::NONE;
224+
}
225+
226+
impl<T: MaybeItem> From<Maybe<T>> for Option<T> {
227+
fn from(value: Maybe<T>) -> Self {
228+
if value.is_some() {
229+
// We can't move out of value so we copy it and forget it in
230+
// order to perform a "manual" move out of value
231+
let ret = unsafe { Some(value.some.assume_init_read()) };
232+
std::mem::forget(value);
233+
ret
234+
} else {
235+
None
236+
}
237+
}
238+
}
239+
240+
impl<T: MaybeItem> From<Option<T>> for Maybe<T> {
241+
fn from(value: Option<T>) -> Self {
242+
<T as MaybeItem>::from_option(value)
243+
}
244+
}
245+
246+
impl<T: MaybeItem + Debug> Debug for Maybe<T> {
247+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248+
if self.is_none() {
249+
write!(f, "Maybe::None")
250+
} else {
251+
write!(f, "Maybe::Some({:?})", unsafe {
252+
self.some.assume_init_ref()
253+
})
254+
}
255+
}
256+
}
257+
258+
impl<T: MaybeItem + Display> Display for Maybe<T> {
259+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260+
if self.is_none() {
261+
write!(f, "Maybe::None")
262+
} else {
263+
write!(f, "Maybe::Some({})", unsafe { self.some.assume_init_ref() })
264+
}
265+
}
266+
}
267+
268+
impl<T: MaybeItem> Drop for Maybe<T> {
269+
fn drop(&mut self) {
270+
T::drop_in_place(self);
271+
}
272+
}
273+
}

0 commit comments

Comments
 (0)