medea_jason/platform/dart/utils/
dart_api.rs

1//! Functionality for calling [`Dart DL API`] from Rust.
2//!
3//! [`Dart DL API`]: https://tinyurl.com/32e7fudh
4
5use core::ffi;
6
7use dart_sys::{
8    Dart_CObject, Dart_DeletePersistentHandle_DL, Dart_FinalizableHandle,
9    Dart_GetError_DL, Dart_Handle, Dart_HandleFinalizer,
10    Dart_HandleFromPersistent_DL, Dart_InitializeApiDL, Dart_IsError_DL,
11    Dart_NewFinalizableHandle_DL, Dart_NewPersistentHandle_DL,
12    Dart_NewUnhandledExceptionError_DL, Dart_PersistentHandle, Dart_Port_DL,
13    Dart_PostCObject_DL, Dart_PropagateError_DL,
14};
15
16/// Initializes usage of Dynamically Linked Dart API.
17///
18/// # Safety
19///
20/// Intended to be called ONLY with [`NativeApi.initializeApiDLData`][1] from
21/// Dart.
22///
23/// [1]: https://api.dart.dev/dart-ffi/NativeApi/initializeApiDLData.html
24pub unsafe fn initialize_api(data: *mut ffi::c_void) -> isize {
25    unsafe { Dart_InitializeApiDL(data) }
26}
27
28/// Allocates a [`Dart_PersistentHandle`] for provided [`Dart_Handle`].
29///
30/// [`Dart_PersistentHandle`]s have the lifetime of the current isolate
31/// unless they are explicitly deallocated.
32///
33/// # Safety
34///
35/// [`initialize_api`] must be called before this function.
36pub unsafe fn new_persistent_handle(
37    object: Dart_Handle,
38) -> Dart_PersistentHandle {
39    let func = unsafe {
40        #[expect(clippy::expect_used, reason = "FFI should be initialized")]
41        Dart_NewPersistentHandle_DL
42            .expect("`dart_api_dl` has not been initialized")
43    };
44    unsafe { func(object) }
45}
46
47/// Allocates a [`Dart_Handle`] in the current scope from the given
48/// [`Dart_PersistentHandle`].
49///
50/// This doesn't affect the provided [`Dart_PersistentHandle`]'s lifetime.
51///
52/// # Safety
53///
54/// [`initialize_api`] must be called before this function.
55pub unsafe fn handle_from_persistent(
56    object: Dart_PersistentHandle,
57) -> Dart_Handle {
58    let func = unsafe {
59        #[expect(clippy::expect_used, reason = "FFI should be initialized")]
60        Dart_HandleFromPersistent_DL
61            .expect("`dart_api_dl` has not been initialized")
62    };
63    unsafe { func(object) }
64}
65
66/// Deallocates the provided [`Dart_PersistentHandle`].
67///
68/// # Safety
69///
70/// [`initialize_api`] must be called before this function.
71pub unsafe fn delete_persistent_handle(object: Dart_Handle) {
72    let func = unsafe {
73        #[expect(clippy::expect_used, reason = "FFI should be initialized")]
74        Dart_DeletePersistentHandle_DL
75            .expect("`dart_api_dl` has not been initialized")
76    };
77    unsafe {
78        func(object);
79    }
80}
81
82/// Posts a `message` on some port. It will contain a [`Dart_CObject`]
83/// object graph rooted in the `message`.
84///
85/// While the `message` is being sent the state of the graph of
86/// [`Dart_CObject`] structures rooted in the `message` should not be
87/// accessed, as the message generation will make temporary modifications to
88/// the data. When the message has been sent the graph will be fully
89/// restored.
90///
91/// If `true` is returned, the `message` was enqueued, and finalizers for
92/// external typed data will eventually run, even if the receiving isolate
93/// shuts down before processing the `message`. If `false` is returned, the
94/// `message` was not enqueued and ownership of external typed data in the
95/// `message` remains with the caller.
96///
97/// # Safety
98///
99/// [`initialize_api`] must be called before this function.
100pub unsafe fn post_c_object(
101    port_id: Dart_Port_DL,
102    message: *mut Dart_CObject,
103) -> bool {
104    let func = unsafe {
105        #[expect(clippy::expect_used, reason = "FFI should be initialized")]
106        Dart_PostCObject_DL.expect("`dart_api_dl` has not been initialized")
107    };
108    unsafe { func(port_id, message) }
109}
110
111/// Allocates a finalizable handle for an object.
112///
113/// This handle has the lifetime of the current isolate group unless the
114/// object pointed to by the handle is garbage collected, in this case the
115/// VM automatically deletes the handle after invoking the callback
116/// associated with the handle.
117///
118/// Once finalizable handle is collected by GC, the provided `callback` is
119/// called. It may be executed on any thread, will have an isolate group, but
120/// won't have the current isolate.
121///
122/// `peer` argument will be provided to the `callback` on finalize.
123///
124/// `external_allocation_size` is a size of the `peer` which can be
125/// calculated via [`mem::size_of()`].
126///
127/// # Safety
128///
129/// [`initialize_api`] must be called before this function.
130///
131/// [`mem::size_of()`]: std::mem::size_of
132pub unsafe fn new_finalizable_handle(
133    object: Dart_Handle,
134    peer: *mut ffi::c_void,
135    external_allocation_size: isize,
136    callback: Dart_HandleFinalizer,
137) -> Dart_FinalizableHandle {
138    let func = unsafe {
139        #[expect(clippy::expect_used, reason = "FFI should be initialized")]
140        Dart_NewFinalizableHandle_DL
141            .expect("`dart_api_dl` has not been initialized")
142    };
143    unsafe { func(object, peer, external_allocation_size, callback) }
144}
145
146/// Checks whether the provided [`Dart_Handle`] represents a Dart error.
147///
148/// Should be called on the current isolate.
149///
150/// # Safety
151///
152/// [`initialize_api`] must be called before this function.
153pub unsafe fn is_error(handle: Dart_Handle) -> bool {
154    let func = unsafe {
155        #[expect(clippy::expect_used, reason = "FFI should be initialized")]
156        Dart_IsError_DL.expect("`dart_api_dl` has not been initialized")
157    };
158    unsafe { func(handle) }
159}
160
161/// Returns the error message from the provided Dart error handle.
162///
163/// Should be called on the current isolate.
164///
165/// Returns a C string containing a Dart error message if the provided
166/// `object` represents a Dart error, or an empty C string ("") otherwise.
167///
168/// # Safety
169///
170/// [`initialize_api`] must be called before this function.
171pub unsafe fn get_error(handle: Dart_Handle) -> *const ffi::c_char {
172    let func = unsafe {
173        #[expect(clippy::expect_used, reason = "FFI should be initialized")]
174        Dart_GetError_DL.expect("`dart_api_dl` has not been initialized")
175    };
176    unsafe { func(handle) }
177}
178
179/// Propagates the given Dart error to the Dart side.
180///
181/// If the provided [`Dart_Handle`] is an unhandled exception error, then it
182/// will be rethrown in the standard way: walking up Dart frames until an
183/// appropriate `catch` block is found, than executing `finally` blocks, and so
184/// on.
185///
186/// # Safety
187///
188/// Intended to be called ONLY with [`NativeApi.initializeApiDLData`][1] from
189/// Dart.
190///
191/// [`initialize_api`] must be called before this function.
192///
193/// [1]: https://api.dart.dev/dart-ffi/NativeApi/initializeApiDLData.html
194pub unsafe fn propagate_error(mut handle: Dart_Handle) {
195    let is_error = unsafe {
196        #[expect(clippy::expect_used, reason = "FFI should be initialized")]
197        Dart_IsError_DL.expect("`dart_api_dl` has not been initialized")
198    };
199
200    let is_error = unsafe { is_error(handle) };
201
202    if !is_error {
203        let make_unhandled = unsafe {
204            #[expect(clippy::expect_used, reason = "FFI should be initialized")]
205            Dart_NewUnhandledExceptionError_DL
206                .expect("`dart_api_dl` has not been initialized")
207        };
208
209        handle = unsafe { make_unhandled(handle) };
210    }
211
212    let propagate = unsafe {
213        #[expect(clippy::expect_used, reason = "FFI should be initialized")]
214        Dart_PropagateError_DL.expect("`dart_api_dl` has not been initialized")
215    };
216    unsafe {
217        propagate(handle);
218    }
219}