Skip to content

Commit d89a3d1

Browse files
authored
Merge pull request #487 from filmor/make-resource-binary
Implement ResourceArc::make_binary
2 parents 263a027 + 6663cb1 commit d89a3d1

File tree

6 files changed

+147
-23
lines changed

6 files changed

+147
-23
lines changed

rustler/src/resource.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use std::mem;
99
use std::ops::Deref;
1010
use std::ptr;
1111

12-
use super::{Decoder, Encoder, Env, Error, NifResult, Term};
12+
use super::{Binary, Decoder, Encoder, Env, Error, NifResult, Term};
1313
use crate::wrapper::{
14-
c_void, NifResourceFlags, MUTABLE_NIF_RESOURCE_HANDLE, NIF_ENV, NIF_RESOURCE_TYPE,
14+
c_void, resource, NifResourceFlags, MUTABLE_NIF_RESOURCE_HANDLE, NIF_ENV, NIF_RESOURCE_TYPE,
1515
};
1616

1717
/// Re-export a type used by the `resource!` macro.
@@ -78,7 +78,7 @@ pub fn open_struct_resource_type<T: ResourceTypeProvider>(
7878
flags: NifResourceFlags,
7979
) -> Option<ResourceType<T>> {
8080
let res: Option<NIF_RESOURCE_TYPE> = unsafe {
81-
crate::wrapper::resource::open_resource_type(
81+
resource::open_resource_type(
8282
env.as_c_arg(),
8383
name.as_bytes(),
8484
Some(resource_destructor::<T>),
@@ -133,8 +133,7 @@ where
133133
/// ResourceTypeProvider implemented for it. See module documentation for info on this.
134134
pub fn new(data: T) -> Self {
135135
let alloc_size = get_alloc_size_struct::<T>();
136-
let mem_raw =
137-
unsafe { crate::wrapper::resource::alloc_resource(T::get_type().res, alloc_size) };
136+
let mem_raw = unsafe { resource::alloc_resource(T::get_type().res, alloc_size) };
138137
let aligned_mem = unsafe { align_alloced_mem_for_struct::<T>(mem_raw) as *mut T };
139138

140139
unsafe { ptr::write(aligned_mem, data) };
@@ -145,9 +144,49 @@ where
145144
}
146145
}
147146

147+
/// Make a resource binary associated with the given resource
148+
///
149+
/// The closure `f` is called with the referenced object and must return a slice with the same
150+
/// lifetime as the object. This means that the slice either has to be derived directly from
151+
/// the instance or that it has to have static lifetime.
152+
pub fn make_binary<'env, 'a, F>(&self, env: Env<'env>, f: F) -> Binary<'env>
153+
where
154+
F: FnOnce(&'a T) -> &'a [u8],
155+
{
156+
// This call is safe because `f` can only return a slice that lives at least as long as
157+
// the given instance of `T`.
158+
unsafe { self.make_binary_unsafe(env, f) }
159+
}
160+
161+
/// Make a resource binary without strict lifetime checking
162+
///
163+
/// The user *must* ensure that the lifetime of the returned slice is at least as long as the
164+
/// lifetime of the referenced instance.
165+
///
166+
/// # Safety
167+
///
168+
/// This function is only safe if the slice that is returned from the closure is guaranteed to
169+
/// live at least as long as the `ResourceArc` instance. If in doubt, use the safe version
170+
/// `ResourceArc::make_binary` which enforces this bound through its signature.
171+
pub unsafe fn make_binary_unsafe<'env, 'a, 'b, F>(&self, env: Env<'env>, f: F) -> Binary<'env>
172+
where
173+
F: FnOnce(&'a T) -> &'b [u8],
174+
{
175+
let bin = f(&*self.inner);
176+
let binary = rustler_sys::enif_make_resource_binary(
177+
env.as_c_arg(),
178+
self.raw,
179+
bin.as_ptr() as *const c_void,
180+
bin.len(),
181+
);
182+
183+
let term = Term::new(env, binary);
184+
Binary::from_term_and_slice(term, bin)
185+
}
186+
148187
fn from_term(term: Term) -> Result<Self, Error> {
149188
let res_resource = match unsafe {
150-
crate::wrapper::resource::get_resource(
189+
resource::get_resource(
151190
term.get_env().as_c_arg(),
152191
term.as_c_arg(),
153192
T::get_type().res,
@@ -157,7 +196,7 @@ where
157196
None => return Err(Error::BadArg),
158197
};
159198
unsafe {
160-
crate::wrapper::resource::keep_resource(res_resource);
199+
resource::keep_resource(res_resource);
161200
}
162201
let casted_ptr = unsafe { align_alloced_mem_for_struct::<T>(res_resource) as *mut T };
163202
Ok(ResourceArc {
@@ -167,12 +206,7 @@ where
167206
}
168207

169208
fn as_term<'a>(&self, env: Env<'a>) -> Term<'a> {
170-
unsafe {
171-
Term::new(
172-
env,
173-
crate::wrapper::resource::make_resource(env.as_c_arg(), self.raw),
174-
)
175-
}
209+
unsafe { Term::new(env, resource::make_resource(env.as_c_arg(), self.raw)) }
176210
}
177211

178212
fn as_c_arg(&mut self) -> *const c_void {
@@ -203,7 +237,7 @@ where
203237
/// resource. The `T` value is not cloned.
204238
fn clone(&self) -> Self {
205239
unsafe {
206-
crate::wrapper::resource::keep_resource(self.raw);
240+
resource::keep_resource(self.raw);
207241
}
208242
ResourceArc {
209243
raw: self.raw,

rustler/src/types/binary.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ unsafe impl Sync for OwnedBinary {}
237237
/// See [module-level doc](index.html) for more information.
238238
#[derive(Copy, Clone)]
239239
pub struct Binary<'a> {
240-
inner: ErlNifBinary,
240+
buf: *const u8,
241+
size: usize,
241242
term: Term<'a>,
242243
}
243244

@@ -257,7 +258,8 @@ impl<'a> Binary<'a> {
257258
)
258259
};
259260
Binary {
260-
inner: owned.0,
261+
buf: owned.0.data,
262+
size: owned.0.size,
261263
term,
262264
}
263265
}
@@ -289,12 +291,26 @@ impl<'a> Binary<'a> {
289291
{
290292
return Err(Error::BadArg);
291293
}
294+
295+
let binary = unsafe { binary.assume_init() };
292296
Ok(Binary {
293-
inner: unsafe { binary.assume_init() },
297+
buf: binary.data,
298+
size: binary.size,
294299
term,
295300
})
296301
}
297302

303+
/// Creates a Binary from a `term` and the associated slice
304+
///
305+
/// The `term` *must* be constructed from the given slice, it is not checked.
306+
pub(crate) unsafe fn from_term_and_slice(term: Term<'a>, binary: &[u8]) -> Self {
307+
Binary {
308+
term,
309+
buf: binary.as_ptr(),
310+
size: binary.len(),
311+
}
312+
}
313+
298314
/// Creates a `Binary` from `term`.
299315
///
300316
/// # Errors
@@ -312,8 +328,11 @@ impl<'a> Binary<'a> {
312328
{
313329
return Err(Error::BadArg);
314330
}
331+
332+
let binary = unsafe { binary.assume_init() };
315333
Ok(Binary {
316-
inner: unsafe { binary.assume_init() },
334+
buf: binary.data,
335+
size: binary.size,
317336
term,
318337
})
319338
}
@@ -326,7 +345,7 @@ impl<'a> Binary<'a> {
326345

327346
/// Extracts a slice containing the entire binary.
328347
pub fn as_slice(&self) -> &'a [u8] {
329-
unsafe { ::std::slice::from_raw_parts(self.inner.data, self.inner.size) }
348+
unsafe { ::std::slice::from_raw_parts(self.buf, self.size) }
330349
}
331350

332351
/// Returns a new view into the same binary.
@@ -339,10 +358,24 @@ impl<'a> Binary<'a> {
339358
/// If `offset + length` is out of bounds, an error will be returned.
340359
pub fn make_subbinary(&self, offset: usize, length: usize) -> NifResult<Binary<'a>> {
341360
let min_len = length.checked_add(offset);
342-
if min_len.ok_or(Error::BadArg)? > self.inner.size {
361+
if min_len.ok_or(Error::BadArg)? > self.size {
343362
return Err(Error::BadArg);
344363
}
345364

365+
Ok(unsafe { self.make_subbinary_unchecked(offset, length) })
366+
}
367+
368+
/// Returns a new view into the same binary.
369+
///
370+
/// This method is an unsafe variant of `Binary::make_subbinary` in that it does not check for
371+
/// `offset + length < self.len()` and always returns a `Binary`.
372+
///
373+
/// # Safety
374+
///
375+
/// If `offset + length` is out of bounds, this call results in *undefined behavior*. The
376+
/// caller has to ensure that `offset + length < self.len()`.
377+
#[allow(unused_unsafe)]
378+
pub unsafe fn make_subbinary_unchecked(&self, offset: usize, length: usize) -> Binary<'a> {
346379
let raw_term = unsafe {
347380
rustler_sys::enif_make_sub_binary(
348381
self.term.get_env().as_c_arg(),
@@ -352,8 +385,12 @@ impl<'a> Binary<'a> {
352385
)
353386
};
354387
let term = unsafe { Term::new(self.term.get_env(), raw_term) };
355-
// This should never fail, as we are always passing in a binary term.
356-
Ok(Binary::from_term(term).ok().unwrap())
388+
389+
Binary {
390+
buf: unsafe { self.buf.add(offset) },
391+
size: length,
392+
term,
393+
}
357394
}
358395
}
359396

rustler_tests/lib/rustler_test.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ defmodule RustlerTest do
3838
def resource_make_immutable(_), do: err()
3939
def resource_immutable_count(), do: err()
4040

41+
def resource_make_with_binaries(), do: err()
42+
def resource_make_binaries(_), do: err()
43+
4144
def make_shorter_subbinary(_), do: err()
4245
def parse_integer(_), do: err()
4346
def binary_new(), do: err()

rustler_tests/native/rustler_test/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ rustler::init!(
3838
test_resource::resource_get_integer_field,
3939
test_resource::resource_make_immutable,
4040
test_resource::resource_immutable_count,
41+
test_resource::resource_make_with_binaries,
42+
test_resource::resource_make_binaries,
4143
test_atom::atom_to_string,
4244
test_atom::atom_equals_ok,
4345
test_atom::binary_to_atom,

rustler_tests/native/rustler_test/src/test_resource.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustler::{Env, ResourceArc};
1+
use rustler::{Binary, Env, ResourceArc};
22
use std::sync::RwLock;
33

44
pub struct TestResource {
@@ -12,9 +12,15 @@ pub struct ImmutableResource {
1212
b: u32,
1313
}
1414

15+
pub struct WithBinaries {
16+
a: [u8; 10],
17+
b: Vec<u8>,
18+
}
19+
1520
pub fn on_load(env: Env) -> bool {
1621
rustler::resource!(TestResource, env);
1722
rustler::resource!(ImmutableResource, env);
23+
rustler::resource!(WithBinaries, env);
1824
true
1925
}
2026

@@ -42,6 +48,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
4248

4349
lazy_static::lazy_static! {
4450
static ref COUNT: AtomicUsize = AtomicUsize::new(0);
51+
static ref STATIC_BIN: [u8; 10] = [0,1,2,3,4,5,6,7,8,9];
4552
}
4653

4754
impl ImmutableResource {
@@ -69,3 +76,35 @@ pub fn resource_make_immutable(u: u32) -> ResourceArc<ImmutableResource> {
6976
pub fn resource_immutable_count() -> u32 {
7077
COUNT.load(Ordering::SeqCst) as u32
7178
}
79+
80+
#[rustler::nif]
81+
pub fn resource_make_with_binaries() -> ResourceArc<WithBinaries> {
82+
ResourceArc::new(WithBinaries {
83+
a: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
84+
b: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
85+
})
86+
}
87+
88+
#[rustler::nif]
89+
pub fn resource_make_binaries(
90+
env: Env,
91+
resource: ResourceArc<WithBinaries>,
92+
) -> (Binary, Binary, Binary) {
93+
// This won't compile:
94+
// let temp = [1,2,3];
95+
// resource.make_binary(env, |_w| &temp)
96+
97+
(
98+
// From slice (embedded in T)
99+
resource.make_binary(env, |w| &w.a),
100+
// From vec (on the heap)
101+
resource.make_binary(env, |w| &w.b),
102+
// From static
103+
resource.make_binary(env, |_| &*STATIC_BIN),
104+
)
105+
}
106+
107+
#[rustler::nif]
108+
pub fn resource_make_binary_from_vec(env: Env, resource: ResourceArc<WithBinaries>) -> Binary {
109+
resource.make_binary(env, |w| &w.b)
110+
}

rustler_tests/test/resource_test.exs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,13 @@ defmodule RustlerTest.ResourceTest do
3535
# Erlang's exact GC should have cleaned all that up.
3636
assert RustlerTest.resource_immutable_count() == 0
3737
end
38+
39+
test "resource binaries" do
40+
res = RustlerTest.resource_make_with_binaries()
41+
42+
{slice, vec, static} = RustlerTest.resource_make_binaries(res)
43+
44+
assert slice == vec
45+
assert vec == static
46+
end
3847
end

0 commit comments

Comments
 (0)