1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
//! Managed expressions.
//!
//! Managed expressions are Wolfram Language expressions created using
//! [`CreateManagedLibraryExpression`][ref/CreateManagedLibraryExpression]<sub>WL</sub>,
//! which are associated with a unique [`Id`] number that is shared with a loaded library.
//!
//! Using [`register_library_expression_manager()`], a library can register a callback
//! function, which will recieve a [`ManagedExpressionEvent`] each time a new managed
//! expression is created or deallocated.
//!
//! The managed expression [`Create(Id)`][ManagedExpressionEvent::Create] event is
//! typically handled by the library to create an instance of some library data type that
//! is associated with the managed expression. When the managed expression is finally
//! deallocated, a [`Drop(Id)`][ManagedExpressionEvent::Drop] event is generated, and
//! the library knows it is safe to free the associated data object.
//!
//! In this way, managed expressions allow memory-management of Rust objects to be
//! performed indirectly based on the lifetime of a Wolfram Language expression.
//!
// TODO: Expand and polish this section: # Alternatives
//
// * Canonical WL expression representation
// * MyStruct[<some raw pointer value>] and manual memory management from WL
//
// Managed expressions are a way for objects that cannot be easily or efficiently
// represented as a Wolfram Language expression to still be associated with the lifetime
// of a Wolfram Language expression.
//
// If an object can be represented as a Wolfram expression, then that is the most
// straightforward thing to do. the
// simplest possible [[ToExpr, FromExpr]].
//
// The simplest alternative to managed expressions to simply convert the
//
//! # Related links
//!
//! * [Managed Library Expressions] section of the LibraryLink documentation.
//! * The [wolfram-library-link managed expressions example](https://github.com/WolframResearch/wolfram-library-link-rs#example-programs).
//!
//! [Managed Library Expressions]: https://reference.wolfram.com/language/LibraryLink/tutorial/InteractionWithWolframLanguage.html#353220453
//! [ref/CreateManagedLibraryExpression]: https://reference.wolfram.com/language/ref/CreateManagedLibraryExpression.html
use ;
use Lazy;
use crate::;
/// Lifecycle events triggered by the creation and deallocation of managed expressions.
/// Unique identifier associated with an instance of a managed library expression.
pub type Id = u32;
/// Register a new callback function for handling managed expression events.
//======================================
// C wrapper functions
//======================================
/// # Implementation note on the reason for this static / "slot" system.
///
/// Having this static is not a direct requirement of the library expression
/// C API, however it is necessary as a workaround for the problem described below.
///
/// `registerLibraryExpressionManager()` expects a callback function of the type:
///
/// ```ignore
/// unsafe extern "C" fn(WolframLibraryData, mbool, mint)
/// ```
///
/// however, for the purpose of providing a more ergonomic and safe wrapper to the user,
/// we want the user to be able to pass `register_library_expression_manager()` a callback
/// function with the type:
///
/// ```ignore
/// fn(ManagedExpressionAction)
/// ```
///
/// This specific problem is an instance of the more general problem of how to expose a
/// user-provided function/closure (non-`extern "C"`) as-if it actually were an
/// `extern "C"` function.
///
/// There are two common ways we could concievably do this:
///
/// 1. Use a macro to generate an `extern "C"` function that calls the user-provided
/// function.
///
/// 2. Use a "trampoline" function (e.g. like async_task_thread_trampoline()) which has
/// the correct `extern "C"` signature, and wraps the user function. This only works
/// if the `extern "C"` function has a parameter that we can control and use to pass
/// in a function pointer to the user-provided function.
///
/// The (1.) strategy is easy to implement, but is undesirable because:
///
/// a) it requires the user to use a macro -- and for subtle reasons (see: this comment)).
/// b) it exposes the underlying `unsafe extern "C" fn(...)` type to the user.
///
/// The (2.) strategy is often a good choice, but cannot be used in this particular
/// case, because their is no way to pass a custom argument to the callback expected by
/// registerLibraryExpressionManager().
///
/// In both the (1.) and (2.) strategies, the solution is to create a single wrapper
/// function for each user function, which is hard-coded to call the user function that
/// it wraps.
///
/// The technique used here is a third strategy:
///
/// 3. Store the user-provided function pointer into a static array, and, instead of
/// having a single `extern "C"` wrapper function, have multiple `extern "C"` wrapper
/// functions, each of which statically access a different index in the static array.
///
/// By using different `extern "C"` functions that access different static data, we
/// can essentially "fake" having an extra function argument that we control.
///
/// This depends on the observation that the callback function pointer is itself a
/// value we control.
///
/// This technique is limited by the fact that the static function pointers must be
/// declared ahead of time (see `def_slot_fn!` below), and so practically there is a
/// somewhat arbitrary limit on how many callbacks can be registered at a time.
///
/// In our case, the *only* data we are able pass through the C API is the static function
/// pointer we are registering; so strategy (3.) is the way to go.
///
/// `SLOTS` has 8 elements, and we define 8 `extern "C" fn slot_<X>(..)` functions that
/// access only the corresponding element in `SLOTS`.
///
/// 8 was picked arbitrarily, on the assumption that 8 different registered types should
/// be sufficient for the vast majority of libraries. Libraries that want to register more
/// than 8 types can use `rtl::registerLibraryExpressionManager` directly as a workaround.
///
/// TODO: Also store the "name" of this manager, and pass it to the user function?
static SLOTS: =
new;
//--------------------------
// Static slot_<X> functions
//--------------------------
def_slot_fn!;
def_slot_fn!;
def_slot_fn!;
def_slot_fn!;
def_slot_fn!;
def_slot_fn!;
def_slot_fn!;
def_slot_fn!;
def_slot_fn!;