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
/*!
# How To: Export typed functions using WXF
The **WXF transport mode** (`#[export(wxf)]`) lets Rust functions receive and
return **typed Rust values** without any manual serialization code. Arguments
arrive from the kernel as a WXF-encoded `ByteArray`; the macro-generated wrapper
deserializes them via `FromWXF`, calls your
function, and serializes the return value via `ToWXF`.
## When to use WXF mode
| Use case | Recommended mode |
|----------|-----------------|
| Scalars, `NumericArray`, images — maximum speed | `#[export]` (native) |
| Arbitrary `Expr` trees, dynamic argument counts | `#[export(wstp)]` |
| Typed Rust structs / enums, `Option`, `Result` | `#[export(wxf)]` |
## Setup
Add `wolfram-export` with the `wxf` feature and `wolfram-serialize` for the derive macros:
```toml
[dependencies]
wolfram-export = { version = "0.6", features = ["wxf"] }
wolfram-serialize = "0.6"
```
## Scalars and standard collections
Primitive types and standard collections work without any extra annotation:
```rust,ignore
# mod scope {
use wolfram_export::export;
#[export(wxf)]
fn add(a: f64, b: f64) -> f64 { a + b }
// Vec<f64> maps to NumericArray["Real64"] on the wire.
#[export(wxf)]
fn dot(a: Vec<f64>, b: Vec<f64>) -> f64 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
#[export(wxf)]
fn greet(name: String) -> String {
format!("Hello, {name}!")
}
# }
```
**Type mapping:**
| Rust | Wolfram wire type |
|------|-------------------|
| `i64`, `i32`, `i16`, `i8` | `Integer` |
| `f64`, `f32` | `Real` |
| `String` / `&str` | `String` |
| `bool` | `True` / `False` |
| `Vec<f64>` | `NumericArray[…, "Real64"]` |
| `Vec<i64>` | `NumericArray[…, "Integer64"]` |
| `Vec<u8>` | `ByteArray[…]` |
| `Vec<T: WxfStruct>` | `{…}` (WL List of associations) |
| `Option<T>` | `<\|"Enum" -> "Some"/"None", "Data" -> {v}\|>` |
| `Result<T, E>` | `<\|"Enum" -> "Ok"/"Err", "Data" -> {v}\|>` |
## Typed structs with `#[derive(ToWXF, FromWXF)]`
Derive `ToWXF` and `FromWXF` on any struct to pass it over the bridge. Named
fields map to an `Association`; field names are converted to camelCase by
default.
```rust,ignore
# mod scope {
use wolfram_export::export;
use wolfram_serialize::{ToWXF, FromWXF};
#[derive(ToWXF, FromWXF, Clone)]
struct Point {
x: f64,
y: f64,
}
#[export(wxf)]
fn echo_point(p: Point) -> Point { p }
#[export(wxf)]
fn translate(p: Point, dx: f64, dy: f64) -> Point {
Point { x: p.x + dx, y: p.y + dy }
}
# }
```
On the Wolfram side these functions accept and return an `Association`:
```wolfram
echoPoint[<|"x" -> 1.0, "y" -> 2.0|>]
(* Returns <|"x" -> 1.0, "y" -> 2.0|> *)
translate[<|"x" -> 0.0, "y" -> 0.0|>, 3.0, 4.0]
(* Returns <|"x" -> 3.0, "y" -> 4.0|> *)
```
## `Option` and `Result`
`Option<T>` and `Result<T, E>` are serialized as tagged associations, so the
kernel can pattern-match on `"Enum"`:
```rust,ignore
# mod scope {
use wolfram_export::export;
// Returns None if n is outside [0, 255].
#[export(wxf)]
fn trim_number(n: f64) -> Option<u8> {
if n >= 0.0 && n <= 255.0 && n.fract() == 0.0 {
Some(n as u8)
} else {
None
}
}
// Returns a descriptive error string on failure.
#[export(wxf)]
fn parse_int(s: String) -> Result<i64, String> {
s.parse::<i64>().map_err(|e| e.to_string())
}
# }
```
## Structured error types with `#[derive(Failure)]`
Derive `Failure` on an error enum to return structured
`Failure["VariantName", <|field -> value, …|>]` expressions that the kernel
can inspect with `Failure`'s built-in machinery:
```rust,ignore
# mod scope {
use wolfram_export::export;
use wolfram_serialize::{Failure, ToWXF};
#[derive(Failure, ToWXF, Debug, Clone)]
enum MathError {
DivisionByZero,
Overflow { lhs: i64, rhs: i64 },
}
#[export(wxf)]
fn safe_divide(a: i64, b: i64) -> Result<i64, MathError> {
if b == 0 {
Err(MathError::DivisionByZero)
} else {
a.checked_div(b).ok_or(MathError::Overflow { lhs: a, rhs: b })
}
}
# }
```
The kernel receives either the integer result or a `Failure`:
```wolfram
safeDivide[10, 2] (* Returns 5 *)
safeDivide[10, 0] (* Returns Failure["DivisionByZero", <||>] *)
```
## Borrowed (zero-copy) struct fields
Structs with `&'de str` or `&'de [u8]` fields implement `FromWXF<'de>` and
borrow directly out of the WXF input buffer — no allocation for the string
data:
```rust,ignore
# mod scope {
use wolfram_export::export;
use wolfram_serialize::FromWXF;
#[derive(FromWXF)]
struct DatasetRef<'a> {
name: &'a str,
values: Vec<f64>,
}
#[export(wxf)]
fn summarize(ds: DatasetRef<'_>) -> String {
format!("{}: {} entries, sum = {}", ds.name, ds.values.len(),
ds.values.iter().sum::<f64>())
}
# }
```
## Loading from Wolfram
Use `generate_loader!` to expose all exported functions through a single loader
entry point:
```rust,ignore
# mod scope {
use wolfram_library_link::generate_loader;
use wolfram_export::export;
generate_loader![load_my_library];
#[export(wxf)]
fn add(a: f64, b: f64) -> f64 { a + b }
# }
```
```wolfram
loadFns = LibraryFunctionLoad[lib, "load_my_library", LinkObject, LinkObject];
fns = loadFns[lib];
(* WXF functions take and return ByteArray on the raw ABI, but the loader
wraps them so you call them with plain Wolfram values: *)
fns["add"][2.0, 3.0] (* Returns 5.0 *)
```
*/