Skip to content

Commit 57b060f

Browse files
committed
Merge branch 'schreter_rust_type_alias' into maizatskyi/2025-06-09-async-io-stream
2 parents 9e10a8f + 9d848a4 commit 57b060f

34 files changed

+937
-25
lines changed

book/src/extern-rust.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,48 @@ mod ffi {
163163

164164
Bounds on a lifetime (like `<'a, 'b: 'a>`) are not currently supported. Nor are
165165
type parameters or where-clauses.
166+
167+
## Type equivalence across bridges
168+
169+
Similar to type aliases for C++ types, it is possible to create type aliases for
170+
previously-exported types via the `extern "Rust"` block in another bridge.
171+
However, current support is very limited:
172+
173+
- The type name must be the same as that of the target type.
174+
- If the target is in a different C++ namespace, then the namespace must be
175+
explicitly specified on the alias, otherwise C++ won't consider the types as
176+
equivalent.
177+
178+
Basically, this is enough to import the type from another bridge and nothing more.
179+
180+
In the first module `crate::mod1`, you export the type:
181+
182+
```rust,noplayground
183+
pub struct MyType {
184+
...
185+
}
186+
187+
#[cxx::bridge(namespace = "mod1")]
188+
mod ffi {
189+
extern "Rust" {
190+
type MyType;
191+
}
192+
}
193+
```
194+
195+
And in another crate/module `mod2`, you can now import the type and use it as
196+
a parameter or a return value in C++ and Rust functions:
197+
198+
```rust,noplayground
199+
#[cxx::bridge(namespace = "mod2")]
200+
mod ffi {
201+
extern "Rust" {
202+
#[namespace = "mod1"]
203+
type MyType = crate::mod1::MyType;
204+
}
205+
206+
extern "C++" {
207+
fn c_func(param: &MyType);
208+
}
209+
}
210+
```

gen/src/namespace.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ pub fn namespace(api: &Api) -> &Namespace {
55
match api {
66
Api::CxxFunction(efn) | Api::RustFunction(efn) => &efn.name.namespace,
77
Api::CxxType(ety) | Api::RustType(ety) => &ety.name.namespace,
8+
Api::TypeAlias(ety) => &ety.name.namespace,
89
Api::Enum(enm) => &enm.name.namespace,
910
Api::Struct(strct) => &strct.name.namespace,
10-
Api::Impl(_) | Api::Include(_) | Api::TypeAlias(_) => Default::default(),
11+
Api::Impl(_) | Api::Include(_) => Default::default(),
1112
}
1213
}

gen/src/write.rs

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use syntax::symbol::{self, Symbol};
1111
use syntax::trivial::{self, TrivialReason};
1212
use syntax::Lang;
1313
use syntax::{
14-
derive, mangle, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Pair, Signature, Struct, Trait,
15-
Type, TypeAlias, Types, Var,
14+
derive, mangle, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Lang, Pair, Signature, Struct,
15+
Trait, Type, TypeAlias, Types, Var,
1616
};
1717

1818
pub fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec<u8> {
@@ -36,6 +36,7 @@ pub fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec<u8> {
3636
fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) {
3737
let needs_forward_declaration = |api: &&Api| match api {
3838
Api::Struct(_) | Api::CxxType(_) | Api::RustType(_) => true,
39+
Api::TypeAlias(ety) => ety.lang == Lang::Rust,
3940
Api::Enum(enm) => !out.types.cxx.contains(&enm.name.rust),
4041
_ => false,
4142
};
@@ -55,6 +56,7 @@ fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) {
5556
Api::Enum(enm) => write_enum_decl(out, enm),
5657
Api::CxxType(ety) => write_struct_using(out, &ety.name),
5758
Api::RustType(ety) => write_struct_decl(out, &ety.name),
59+
Api::TypeAlias(ety) => write_struct_decl(out, &ety.name),
5860
_ => unreachable!(),
5961
}
6062
}
@@ -129,8 +131,17 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
129131
out.next_section();
130132
for api in apis {
131133
if let Api::TypeAlias(ety) = api {
132-
if let Some(reasons) = out.types.required_trivial.get(&ety.name.rust) {
133-
check_trivial_extern_type(out, ety, reasons);
134+
match ety.lang {
135+
Lang::Cxx => {
136+
if let Some(reasons) = out.types.required_trivial.get(&ety.name.rust) {
137+
check_trivial_extern_type(out, ety, reasons)
138+
}
139+
}
140+
Lang::Rust => {
141+
// nothing to write here, the alias is only used to generate
142+
// forward declaration in C++ (so C++ shims for Rust functions
143+
// using the type compile correctly).
144+
}
134145
}
135146
}
136147
}
@@ -223,7 +234,7 @@ fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
223234
Type::SliceRef(_) => out.builtin.rust_slice = true,
224235
Type::Array(_) => out.include.array = true,
225236
Type::Ref(_) | Type::Void(_) | Type::Ptr(_) => {}
226-
Type::Future(_) => out.include.kj_rs = true,
237+
Type::Future(_) | Type::Own(_) => out.include.kj_rs = true,
227238
}
228239
}
229240
}
@@ -1030,6 +1041,16 @@ fn write_rust_function_shim_impl(
10301041
return;
10311042
}
10321043
writeln!(out, " {{");
1044+
sig.args
1045+
.iter()
1046+
.filter(|arg| matches!(arg.ty, Type::Own(_)))
1047+
.for_each(|arg_own| {
1048+
writeln!(
1049+
out,
1050+
" KJ_ASSERT({}.get() != nullptr, \"Cannot pass a null Own to Rust\");",
1051+
arg_own.name.cxx
1052+
);
1053+
});
10331054
for arg in &sig.args {
10341055
if arg.ty != RustString && out.types.needs_indirect_abi(&arg.ty) {
10351056
out.include.utility = true;
@@ -1228,6 +1249,11 @@ fn write_type(out: &mut OutFile, ty: &Type) {
12281249
write_type(out, &ptr.inner);
12291250
write!(out, ">");
12301251
}
1252+
Type::Own(ptr) => {
1253+
write!(out, "::kj::Own<");
1254+
write_type(out, &ptr.inner);
1255+
write!(out, ">");
1256+
}
12311257
Type::SharedPtr(ptr) => {
12321258
write!(out, "::std::shared_ptr<");
12331259
write_type(out, &ptr.inner);
@@ -1328,6 +1354,7 @@ fn write_space_after_type(out: &mut OutFile, ty: &Type) {
13281354
Type::Ident(_)
13291355
| Type::RustBox(_)
13301356
| Type::UniquePtr(_)
1357+
| Type::Own(_)
13311358
| Type::SharedPtr(_)
13321359
| Type::WeakPtr(_)
13331360
| Type::Str(_)
@@ -1404,6 +1431,7 @@ fn write_generic_instantiations(out: &mut OutFile) {
14041431
ImplKey::RustBox(ident) => write_rust_box_extern(out, ident),
14051432
ImplKey::RustVec(ident) => write_rust_vec_extern(out, ident),
14061433
ImplKey::UniquePtr(ident) => write_unique_ptr(out, ident),
1434+
ImplKey::Own(ident) => write_kj_own(out, ident),
14071435
ImplKey::SharedPtr(ident) => write_shared_ptr(out, ident),
14081436
ImplKey::WeakPtr(ident) => write_weak_ptr(out, ident),
14091437
ImplKey::CxxVector(ident) => write_cxx_vector(out, ident),
@@ -1615,6 +1643,34 @@ fn write_rust_vec_impl(out: &mut OutFile, key: NamedImplKey) {
16151643
writeln!(out, "}}");
16161644
}
16171645

1646+
// Writes static assertion that we do not use an Own with a static disposer
1647+
fn write_kj_own(out: &mut OutFile, key: NamedImplKey) {
1648+
let ident = key.rust;
1649+
let resolve = out.types.resolve(ident);
1650+
let inner = resolve.name.to_fully_qualified();
1651+
let instance = resolve.name.to_symbol();
1652+
1653+
out.include.utility = true;
1654+
out.include.kj_rs = true;
1655+
1656+
// Static disposers are not supported, only Owns containing 2 pointers are allowed
1657+
writeln!(
1658+
out,
1659+
"static_assert(sizeof(::kj::Own<{}>) == 2 * sizeof(void *), \"Static disposers for Own are not supported in workerd-cxx\");",
1660+
inner,
1661+
);
1662+
writeln!(
1663+
out,
1664+
"static_assert(alignof(::kj::Own<{}>) == sizeof(void *), \"Static disposers for Own are not supported in workerd-cxx\");",
1665+
inner,
1666+
);
1667+
writeln!(
1668+
out,
1669+
"static_assert(!::std::is_base_of<::kj::Refcounted, {}>::value, \"Value must not inherit from kj::Refcounted\");",
1670+
inner
1671+
);
1672+
}
1673+
16181674
fn write_unique_ptr(out: &mut OutFile, key: NamedImplKey) {
16191675
let ty = UniquePtr::Ident(key.rust);
16201676
write_unique_ptr_common(out, ty);

kj-rs/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ mod awaiter;
1414
mod future;
1515
mod promise;
1616
mod waker;
17+
mod own;
1718

1819
pub mod repr {
1920
pub use crate::future::repr::*;
21+
pub use crate::own::repr::*;
2022
}
2123

2224
pub type Result<T> = std::io::Result<T>;

kj-rs/own.c++

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include "own.h"
2+
3+
extern "C" {
4+
// As of right now, this function works and passes all tests, destroying all tested objects correctly.
5+
// This works because disposers work by virtual call, and the disposer is created during creation of
6+
// the Own, so the type of the Own at destruction doesn't actually matter for Owns that do not use a
7+
// static disposer, which are not currently supported by workerd-cxx.
8+
void cxxbridge$kjrs$own$drop(void *own) {
9+
reinterpret_cast<kj::Own<void> *>(own)->~Own();
10+
}
11+
}

kj-rs/own.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#pragma once
2+
3+
#include <kj/memory.h>
4+
5+
extern "C" {
6+
void cxxbridge$kjrs$own$drop(void* own);
7+
}

kj-rs/own.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//! The `workerd-cxx` module containing the [`Own<T>`] type, which is bindings to the `kj::Own<T>` C++ type
2+
3+
use static_assertions::{assert_eq_align, assert_eq_size};
4+
5+
assert_eq_size!(repr::Own<()>, [*const (); 2]);
6+
assert_eq_align!(repr::Own<()>, *const ());
7+
8+
pub mod repr {
9+
use std::ffi::c_void;
10+
use std::fmt::{self, Debug, Display};
11+
use std::hash::{Hash, Hasher};
12+
use std::ops::Deref;
13+
use std::ops::DerefMut;
14+
use std::pin::Pin;
15+
use std::ptr::NonNull;
16+
17+
/// A [`Own<T>`] represents the `kj::Own<T>`. It is a smart pointer to an opaque C++ type.
18+
/// Safety:
19+
/// - Passing a null `kj::Own` to rust is considered unsafe from the C++ side,
20+
/// and it is required that this invariant is upheld in C++ code.
21+
/// - Currently, it is runtime asserted in the bridge macro that no null Own can be passed
22+
/// to Rust
23+
#[repr(C)]
24+
pub struct Own<T> {
25+
disposer: *const c_void,
26+
ptr: NonNull<T>,
27+
}
28+
29+
/// Public-facing Own api
30+
impl<T> Own<T> {
31+
/// Returns a mutable pinned reference to the object owned by this [`Own`]
32+
/// if any, otherwise None.
33+
pub fn as_mut(&mut self) -> Pin<&mut T> {
34+
// Safety: Passing a null kj::Own to Rust from C++ is not supported.
35+
unsafe {
36+
let mut_reference = self.ptr.as_mut();
37+
Pin::new_unchecked(mut_reference)
38+
}
39+
}
40+
41+
/// Returns a mutable pinned reference to the object owned by this
42+
/// [`Own`].
43+
///
44+
/// ```compile_fail
45+
/// let mut own = ffi::cxx_kj_own();
46+
/// let pin1 = own.pin_mut();
47+
/// let pin2 = own.pin_mut();
48+
/// pin1.set_data(12); // Causes a compile fail, because we invalidated the first borrow
49+
/// ```
50+
///
51+
/// ```compile_fail
52+
///
53+
/// let mut own = ffi::cxx_kj_own();
54+
/// let pin = own.pin_mut();
55+
/// let moved = own;
56+
/// own.set_data(143); // Compile fail, because we tried using a moved object
57+
/// ```
58+
pub fn pin_mut(&mut self) -> Pin<&mut T> {
59+
self.as_mut()
60+
}
61+
62+
/// Returns a raw const pointer to the object owned by this [`Own`]
63+
#[must_use]
64+
pub fn as_ptr(&self) -> *const T {
65+
self.ptr.as_ptr().cast()
66+
}
67+
}
68+
69+
impl<T> AsRef<T> for Own<T> {
70+
/// Returns a reference to the object owned by this [`Own`] if any,
71+
/// otherwise None.
72+
fn as_ref(&self) -> &T {
73+
// Safety: Passing a null kj::Own to Rust from C++ is not supported.
74+
unsafe { self.ptr.as_ref() }
75+
}
76+
}
77+
78+
unsafe impl<T> Send for Own<T> where T: Send {}
79+
80+
unsafe impl<T> Sync for Own<T> where T: Sync {}
81+
82+
impl<T> Deref for Own<T> {
83+
type Target = T;
84+
85+
fn deref(&self) -> &Self::Target {
86+
self.as_ref()
87+
}
88+
}
89+
90+
impl<T> DerefMut for Own<T>
91+
where
92+
T: Unpin,
93+
{
94+
fn deref_mut(&mut self) -> &mut Self::Target {
95+
Pin::into_inner(self.as_mut())
96+
}
97+
}
98+
99+
// Own<T> is safe to implement Unpin because moving the Own doesn't move the pointee, and
100+
// the drop implentation doesn't depend on the Own's location, because it's handed by virtual dispatch
101+
impl<T> Unpin for Own<T> {}
102+
103+
impl<T> Debug for Own<T> {
104+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105+
write!(f, "Own(ptr: {:p}, disposer: {:p})", self.ptr, self.disposer)
106+
}
107+
}
108+
109+
impl<T> Display for Own<T>
110+
where
111+
T: Display,
112+
{
113+
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
114+
Display::fmt(self.as_ref(), formatter)
115+
}
116+
}
117+
118+
impl<T> PartialEq for Own<T>
119+
where
120+
T: PartialEq,
121+
{
122+
fn eq(&self, other: &Self) -> bool {
123+
self.as_ref() == other.as_ref()
124+
}
125+
}
126+
127+
impl<T> Eq for Own<T> where T: Eq {}
128+
129+
impl<T> PartialOrd for Own<T>
130+
where
131+
T: PartialOrd,
132+
{
133+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
134+
PartialOrd::partial_cmp(&self.as_ref(), &other.as_ref())
135+
}
136+
}
137+
138+
impl<T> Ord for Own<T>
139+
where
140+
T: Ord,
141+
{
142+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
143+
Ord::cmp(&self.as_ref(), &other.as_ref())
144+
}
145+
}
146+
147+
impl<T> Hash for Own<T>
148+
where
149+
T: Hash,
150+
{
151+
fn hash<H: Hasher>(&self, state: &mut H) {
152+
self.as_ref().hash(state);
153+
}
154+
}
155+
156+
impl<T> Drop for Own<T> {
157+
fn drop(&mut self) {
158+
unsafe extern "C" {
159+
#[link_name = "cxxbridge$kjrs$own$drop"]
160+
fn __drop(this: *mut c_void);
161+
}
162+
163+
let this = std::ptr::from_mut::<Self>(self).cast::<c_void>();
164+
unsafe {
165+
__drop(this);
166+
}
167+
}
168+
}
169+
}

0 commit comments

Comments
 (0)