-
Notifications
You must be signed in to change notification settings - Fork 1
[workerd-cxx] Add kj::Maybe type to kj-rs #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
e674fb2
to
2944364
Compare
kj-rs/tests/test-maybe.c++
Outdated
// Static var to return non-dangling pointer without heap allocating | ||
int64_t var = 14; | ||
|
||
// Returns a heap pointer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: s/heap pointer/pointer-to-static/
kj-rs/maybe.rs
Outdated
fn is_niche(value: &MaybeUninit<Self>) -> bool; | ||
} | ||
|
||
// In `kj`, null pointers are considered niche, and `kj::none` if null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like there's actually any specialization of Maybe<T*>
. In fact, when I add this to kj/common-test.c++, it passes:
Maybe<int*> m = static_cast<int*>(nullptr);
KJ_EXPECT(m != kj::none);
auto* p = KJ_ASSERT_NONNULL(m);
KJ_EXPECT(p == nullptr);
(And yes, this is the opposite semantics of Maybe<Own<T>>
, /sigh.)
FWIW, grepping for Maybe<[^>]+\*>
in our codebase yields no results, so it looks like we don't actually use Maybe<T*>
anywhere. Which makes sense, because we should probably be using Maybe<T&>
, or a raw pointer, in such cases.
I wonder if we should just static_assert
in our generated code that no one is using Maybe<T*>
, with an error message suggesting the author use Maybe<T&>
? That would avoid having to deal with this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A static assert is reasonable, and I can remove the Rust treatment of T*
as special.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also change the check_type function to give an error here, and recommend Maybe<T&>
kj-rs/own.rs
Outdated
} | ||
|
||
/// This type allows me to expose the same API that [`NonNull`] does while maintaining the invariant |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this sentence might have been cut off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was meant for the newtype I made to replace NonNull
in the Own
definition, it must've found its way there when I was shuffling things around for that.
|
||
assert_eq_size!(repr::Own<()>, [*const (); 2]); | ||
assert_eq_align!(repr::Own<()>, *const ()); | ||
|
||
/// When we want to use an `Own`, we want the guarantee of being not null only | ||
/// in direct `Own<T>`, not Maybe<Own<T>>, and using a [`NonNull`] in `Own` | ||
/// but allowing Nulls for Niche Value Optimization is undefined behavior. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes me curious, do you know how Option<Box<T>>
handles this? I would guess it encounters the same issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option<Box<T>>
being an enum means the Rust compiler knows it can optimize, but since Maybe
is a struct we have to manually control this behavior. This makes me wonder if it's actually possible to use an enum... I'm going to do some testing on that side of things. I'm still inclined to say no, since using an enum may result in Rust optimizing things when we don't want it to. The current solution maintains absolute control over layout.
kj-rs/maybe.rs
Outdated
|
||
impl_maybe_item_for_has_niche!(crate::Own<T>, &T, &mut T, *const T, *mut T); | ||
impl_maybe_item_for_primitive!( | ||
u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize, bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f32
, f64
, and char
, too?
I guess ()
wouldn't be representable in C++ (Maybe<void>
).
gen/src/write.rs
Outdated
@@ -1669,6 +1676,17 @@ fn write_kj_own(out: &mut OutFile, key: NamedImplKey) { | |||
); | |||
} | |||
|
|||
// cxx boilerplate function for possible drop trait | |||
// TODO: Add asserts and function generation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mention elsewhere that perhaps an assertion could be that no one is actually using Maybe<T*>
.
macro/src/expand.rs
Outdated
@@ -647,7 +650,7 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { | |||
let call = if indirect_return { | |||
let ret = expand_extern_type(efn.ret.as_ref().unwrap(), types, true); | |||
setup.extend(quote_spanned! {span=> | |||
let mut __return = ::cxx::core::mem::MaybeUninit::<#ret>::uninit(); | |||
let mut __return = ::cxx::core::mem::MaybeUninit::<#ret>::zeroed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was related to the non-deterministic test failure you mentioned, right?
I think it'd be really good to understand this better, since it looks like this affects much more than just the kj::Maybe
integration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is the change I made that fixed the non-deterministic test failure. It's probably possible to scope this change to only happen if the return type is Maybe
, if necessary. I would like to understand the cause of the bug better as well, because this could be unnecessary, but it's the only thing that fixed it, or even got close.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible this was from the incorrect Maybe
pointer code too. I'm testing that right now.
syntax/check.rs
Outdated
} | ||
Type::Ptr(_ptr) => { | ||
return; | ||
// TODO: ("Reminder to check pointer type later") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder to make sure these TODOs are addressed. Should we be calling check_type_ptr()
and check_type_ref()
? I see those two functions do exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think until we have a good reason to not follow cxx's rules around pointer/reference types using those functions is probably best, yeah.
if self.is_none() { | ||
write!(f, "kj::None") | ||
} else { | ||
write!(f, "kj::Some({:?})", unsafe { self.some.assume_init_ref() }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit for discussion: Perhaps these should be lowercased, like kj::none
(the value instead of its type kj::None
) and kj::some()
(the function instead of kj::Some
, which doesn't actually exist)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to have it more accurately represent Rust naming conventions here, with capitalized variants, as if it was a Rust enum. That's ideally the way we want to work with Maybe
, but it does raise questions about when to use what conventions, where, and how. Keeping it consistent with C++ is probably worth it, since that's where we're migrating from, but should that change in the future?
kj-rs/tests/test_maybe.rs
Outdated
fn test_some_ptr() { | ||
let maybe = ffi::return_maybe_ptr_some(); | ||
assert!(maybe.is_some()); | ||
assert!(!maybe.is_none()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also assert that the pointed-to-value is indeed 14? I have a suspicion it won't be, due to my observation that there's no actual Maybe<T*>
specialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's definitely a good idea.
2944364
to
3990b31
Compare
No description provided.