rustler/types/binary.rs
1//! Safe wrappers around Erlang binaries.
2//!
3//! Rustler provides three binary types: [`Binary`], [`NewBinary`] and
4//! [`OwnedBinary`]. All represent a contiguous region `u8`s, and they all use
5//! the Erlang allocator. The primary difference between them is their ownership
6//! semantics.
7//!
8//! The _owned_ in `OwnedBinary` refers to the fact that it owns the binary it
9//! wraps. The _owner_ of an `OwnedBinary` is free to modify its contents. Ownership
10//! lasts until it is dropped or consumed by converting it into a regular
11//! `Binary`. An `OwnedBinary` cannot be copied or cloned and is thus always moved.
12//!
13//! The `Binary` type is an immutable shared-reference to a binary. `Binary`s are
14//! cheap to copy: all copies of a `Binary` point to the original `Binary`'s
15//! data. Additionally, a `Binary`'s lifetime is tied to that of the NIF's [`Env`],
16//! preventing outstanding references to the data after a NIF returns.
17//!
18//! `NewBinary` is a way of creating a `Binary` without going via `OwnedBinary`.
19//! This can improve performance, since `NewBinary`s can be allocated on the
20//! heap if they are small. Unlike `OwnedBinary`, `NewBinary`s lifetime is tied
21//! to that of the NIF's [`Env`]. `NewBinary` must be converted to a `Binary`
22//! or directly to a `Term` before it can be passed to Erlang.
23//!
24//! # Examples
25//!
26//! Constructing an `OwnedBinary`:
27//!
28//! ```no_run
29//! # use rustler::OwnedBinary;
30//! {
31//! let mut bin = OwnedBinary::new(5).expect("allocation failed");
32//! bin.as_mut_slice().copy_from_slice("hello".as_bytes());
33//! } // <- `bin` is dropped here
34//! ```
35//!
36//! The following NIF takes a binary as its only parameter and returns a new binary
37//! where each element is exclusive-or'ed with a constant:
38//!
39//! ```no_run
40//! # use rustler::{Env, OwnedBinary, Binary, NifResult, Error};
41//! #[rustler::nif]
42//! fn xor_example<'a>(env: Env<'a>, bin: Binary<'a>) -> NifResult<Binary<'a>> {
43//! let mut owned: OwnedBinary = bin.to_owned().ok_or(Error::Term(Box::new("no mem")))?;
44//! for byte in owned.as_mut_slice() {
45//! *byte ^= 0xAA;
46//! }
47//!
48//! // Ownership of `owned`'s data is transferred to `env` on the
49//! // following line, so no additional heap allocations are incurred.
50//! Ok(Binary::from_owned(owned, env))
51//! }
52//! ```
53//!
54//! The contents of a newly-allocated `OwnedBinary` is not initialized to any
55//! particular value. If your usage of the binary requires the it's data to be
56//! zeroed, for example, then you must explicit zero it. In this example, we
57//! manually zeroize the binary before passing it as slice to a third party
58//! function.
59//!
60//! ```no_run
61//! # fn some_third_party_api(buf: &mut [u8]) {
62//! # for elem in buf {
63//! # if *elem == 0 { *elem = 1 } else { panic!("Not a zero!") }
64//! # }
65//! # }
66//! # use rustler::{Env, OwnedBinary, Binary, NifResult, Error};
67//! #[rustler::nif]
68//! fn wrapper_for_some_<'a>(env: Env<'a>) -> NifResult<Binary<'a>> {
69//! let mut owned = OwnedBinary::new(100).ok_or(Error::Term(Box::new("no mem")))?;
70//! for byte in owned.as_mut_slice() {
71//! *byte = 0;
72//! }
73//!
74//! // Some third party API which requires the slice to be all zeros on entry.
75//! some_third_party_api(owned.as_mut_slice());
76//!
77//! // The imaginary API call presumedly filled in our binary with meaningful
78//! // data, so let's return it.
79//! Ok(Binary::from_owned(owned, env))
80//! }
81//!
82//! ```
83//!
84//! [`Binary`]: struct.Binary.html
85//! [`Env`]: ../../env/struct.Env.html
86//! [`OwnedBinary`]: struct.OwnedBinary.html
87
88use crate::{
89 sys::{
90 enif_inspect_binary, enif_inspect_iolist_as_binary, enif_make_binary, enif_make_sub_binary,
91 enif_release_binary,
92 },
93 wrapper::binary::{alloc, new_binary, realloc, ErlNifBinary},
94 Decoder, Encoder, Env, Error, NifResult, Term,
95};
96use std::{
97 borrow::{Borrow, BorrowMut},
98 hash::{Hash, Hasher},
99 io::Write,
100 mem::MaybeUninit,
101 ops::{Deref, DerefMut},
102};
103
104/// An mutable smart-pointer to an Erlang binary.
105///
106/// See [module-level doc](index.html) for more information.
107pub struct OwnedBinary(ErlNifBinary);
108
109impl OwnedBinary {
110 pub unsafe fn from_raw(inner: ErlNifBinary) -> OwnedBinary {
111 OwnedBinary(inner)
112 }
113
114 /// Allocates a new `OwnedBinary` with size `size`.
115 ///
116 /// Memory is not initialized. If uninitialized memory is undesirable, set it
117 /// manually.
118 ///
119 /// # Errors
120 ///
121 /// If allocation fails, `None` is returned.
122 pub fn new(size: usize) -> Option<OwnedBinary> {
123 unsafe { alloc(size) }.map(OwnedBinary)
124 }
125
126 /// Copies `src`'s data into a new `OwnedBinary`.
127 ///
128 /// # Errors
129 ///
130 /// If allocation fails, `None` is returned.
131 pub fn from_unowned(src: &Binary) -> Option<OwnedBinary> {
132 OwnedBinary::new(src.len()).map(|mut b| {
133 b.as_mut_slice().copy_from_slice(src);
134 b
135 })
136 }
137
138 /// Attempts to reallocate `self` with the new size.
139 ///
140 /// Memory outside the range of the original binary will not be initialized. If
141 /// uninitialized memory is undesirable, set it manually.
142 ///
143 /// # Errors
144 ///
145 /// If reallocation fails, `false` is returned. Data remains intact on error.
146 #[must_use]
147 pub fn realloc(&mut self, size: usize) -> bool {
148 unsafe { realloc(&mut self.0, size) }
149 }
150
151 /// Attempts to reallocate `self` with the new size.
152 ///
153 /// If reallocation fails, it will perform a copy instead.
154 ///
155 /// Memory outside the range of the original binary will not be initialized. If
156 /// uninitialized memory is undesirable, set it manually.
157 pub fn realloc_or_copy(&mut self, size: usize) {
158 if !self.realloc(size) {
159 let mut new = OwnedBinary::new(size).unwrap();
160 if let Ok(num_written) = new.as_mut_slice().write(self.as_slice()) {
161 if !(num_written == self.len() || num_written == new.len()) {
162 panic!("Could not copy binary");
163 }
164 ::std::mem::swap(&mut self.0, &mut new.0);
165 } else {
166 panic!("Could not copy binary");
167 }
168 }
169 }
170
171 /// Extracts a slice containing the entire binary.
172 pub fn as_slice(&self) -> &[u8] {
173 unsafe { ::std::slice::from_raw_parts(self.0.data, self.0.size) }
174 }
175
176 /// Extracts a mutable slice of the entire binary.
177 pub fn as_mut_slice(&mut self) -> &mut [u8] {
178 unsafe { ::std::slice::from_raw_parts_mut(self.0.data, self.0.size) }
179 }
180
181 /// Consumes `self` and returns an immutable `Binary`.
182 ///
183 /// This method is the mirror of [`Binary::from_owned`], and they can be used
184 /// interchangeably.
185 ///
186 /// [`Binary::from_owned`]: struct.Binary.html#method.from_owned
187 pub fn release(self, env: Env) -> Binary {
188 Binary::from_owned(self, env)
189 }
190}
191
192impl Borrow<[u8]> for OwnedBinary {
193 fn borrow(&self) -> &[u8] {
194 self.as_slice()
195 }
196}
197impl BorrowMut<[u8]> for OwnedBinary {
198 fn borrow_mut(&mut self) -> &mut [u8] {
199 self.as_mut_slice()
200 }
201}
202impl Deref for OwnedBinary {
203 type Target = [u8];
204 fn deref(&self) -> &[u8] {
205 self.as_slice()
206 }
207}
208impl DerefMut for OwnedBinary {
209 fn deref_mut(&mut self) -> &mut [u8] {
210 self.as_mut_slice()
211 }
212}
213impl Hash for OwnedBinary {
214 fn hash<H: Hasher>(&self, state: &mut H) {
215 self.as_slice().hash(state);
216 }
217}
218impl PartialEq for OwnedBinary {
219 fn eq(&self, other: &Self) -> bool {
220 self.as_slice() == other.as_slice()
221 }
222}
223impl Eq for OwnedBinary {}
224impl PartialEq<Binary<'_>> for OwnedBinary {
225 fn eq(&self, other: &Binary) -> bool {
226 self.as_slice() == other.as_slice()
227 }
228}
229
230impl Drop for OwnedBinary {
231 fn drop(&mut self) {
232 unsafe { enif_release_binary(&mut self.0) };
233 }
234}
235
236unsafe impl Send for OwnedBinary {}
237unsafe impl Sync for OwnedBinary {}
238
239impl FromIterator<u8> for OwnedBinary {
240 fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
241 let mut iter = iter.into_iter();
242 let (lower, upper) = iter.size_hint();
243 let mut bin = OwnedBinary::new(upper.unwrap_or(lower)).expect("Allocation failed");
244 let mut i = 0;
245 loop {
246 match iter.next() {
247 None => {
248 if bin.len() != i {
249 bin.realloc_or_copy(i);
250 }
251 return bin;
252 }
253 Some(x) => {
254 if bin.len() <= i {
255 bin.realloc_or_copy(i + i / 2 + 1);
256 }
257 bin.as_mut_slice()[i] = x;
258 i += 1;
259 }
260 }
261 }
262 }
263}
264
265/// An immutable smart-pointer to an Erlang binary.
266///
267/// See [module-level doc](index.html) for more information.
268#[derive(Copy, Clone)]
269pub struct Binary<'a> {
270 buf: *const u8,
271 size: usize,
272 term: Term<'a>,
273}
274
275impl<'a> Binary<'a> {
276 /// Consumes `owned` and returns an immutable `Binary`.
277 #[inline]
278 pub fn from_owned(owned: OwnedBinary, env: Env<'a>) -> Self {
279 // We are transferring ownership of `owned`'s data to the
280 // environment. Therefore, we need to prevent `owned`'s destructor being
281 // called at the end of this scope. The least error-prone solution (compared
282 // to `mem::forget()`) is to wrap `owned` in a `ManuallyDrop` and EXPLICITLY
283 // NOT CALL `ManuallyDrop::drop()`.
284 let mut owned = std::mem::ManuallyDrop::new(owned);
285 let term = unsafe { Term::new(env, enif_make_binary(env.as_c_arg(), &mut owned.0)) };
286 Binary {
287 buf: owned.0.data,
288 size: owned.0.size,
289 term,
290 }
291 }
292
293 /// Copies `self`'s data into a new `OwnedBinary`.
294 ///
295 /// # Errors
296 ///
297 /// If allocation fails, an error will be returned.
298 #[allow(clippy::wrong_self_convention)]
299 #[inline]
300 pub fn to_owned(&self) -> Option<OwnedBinary> {
301 OwnedBinary::from_unowned(self)
302 }
303
304 /// Creates a `Binary` from `term`.
305 ///
306 /// # Errors
307 ///
308 /// If `term` is not a binary, an error will be returned.
309 #[inline]
310 pub fn from_term(term: Term<'a>) -> Result<Self, Error> {
311 let mut binary = MaybeUninit::uninit();
312 if unsafe {
313 enif_inspect_binary(
314 term.get_env().as_c_arg(),
315 term.as_c_arg(),
316 binary.as_mut_ptr(),
317 )
318 } == 0
319 {
320 return Err(Error::BadArg);
321 }
322
323 let binary = unsafe { binary.assume_init() };
324 Ok(Binary {
325 buf: binary.data,
326 size: binary.size,
327 term,
328 })
329 }
330
331 /// Creates a Binary from a `term` and the associated slice
332 ///
333 /// The `term` *must* be constructed from the given slice, it is not checked.
334 pub(crate) unsafe fn from_term_and_slice(term: Term<'a>, binary: &[u8]) -> Self {
335 Binary {
336 term,
337 buf: binary.as_ptr(),
338 size: binary.len(),
339 }
340 }
341
342 /// Creates a `Binary` from `term`.
343 ///
344 /// # Errors
345 ///
346 /// If `term` is not an `iolist`, an error will be returned.
347 #[inline]
348 pub fn from_iolist(term: Term<'a>) -> Result<Self, Error> {
349 let mut binary = MaybeUninit::uninit();
350 if unsafe {
351 enif_inspect_iolist_as_binary(
352 term.get_env().as_c_arg(),
353 term.as_c_arg(),
354 binary.as_mut_ptr(),
355 )
356 } == 0
357 {
358 return Err(Error::BadArg);
359 }
360
361 let binary = unsafe { binary.assume_init() };
362 Ok(Binary {
363 buf: binary.data,
364 size: binary.size,
365 term,
366 })
367 }
368
369 /// Returns an Erlang term representation of `self`.
370 #[allow(clippy::wrong_self_convention)]
371 #[inline]
372 pub fn to_term<'b>(&self, env: Env<'b>) -> Term<'b> {
373 self.term.in_env(env)
374 }
375
376 /// Extracts a slice containing the entire binary.
377 #[inline]
378 pub fn as_slice(&self) -> &'a [u8] {
379 unsafe { ::std::slice::from_raw_parts(self.buf, self.size) }
380 }
381
382 /// Returns a new view into the same binary.
383 ///
384 /// This method is analogous to subslicing (e.g. `some_data[offset..length]`) in
385 /// that it does not copy nor allocate data.
386 ///
387 /// # Errors
388 ///
389 /// If `offset + length` is out of bounds, an error will be returned.
390 #[inline]
391 pub fn make_subbinary(&self, offset: usize, length: usize) -> NifResult<Binary<'a>> {
392 let min_len = length.checked_add(offset);
393 if min_len.ok_or(Error::BadArg)? > self.size {
394 return Err(Error::BadArg);
395 }
396
397 Ok(unsafe { self.make_subbinary_unchecked(offset, length) })
398 }
399
400 /// Returns a new view into the same binary.
401 ///
402 /// This method is an unsafe variant of `Binary::make_subbinary` in that it does not check for
403 /// `offset + length < self.len()` and always returns a `Binary`.
404 ///
405 /// # Safety
406 ///
407 /// If `offset + length` is out of bounds, this call results in *undefined behavior*. The
408 /// caller has to ensure that `offset + length < self.len()`.
409 #[allow(unused_unsafe)]
410 pub unsafe fn make_subbinary_unchecked(&self, offset: usize, length: usize) -> Binary<'a> {
411 let raw_term = unsafe {
412 enif_make_sub_binary(
413 self.term.get_env().as_c_arg(),
414 self.term.as_c_arg(),
415 offset,
416 length,
417 )
418 };
419 let term = unsafe { Term::new(self.term.get_env(), raw_term) };
420
421 Binary {
422 buf: unsafe { self.buf.add(offset) },
423 size: length,
424 term,
425 }
426 }
427}
428
429impl Borrow<[u8]> for Binary<'_> {
430 fn borrow(&self) -> &[u8] {
431 self.as_slice()
432 }
433}
434impl Deref for Binary<'_> {
435 type Target = [u8];
436 fn deref(&self) -> &[u8] {
437 self.as_slice()
438 }
439}
440
441impl<'a> Decoder<'a> for Binary<'a> {
442 fn decode(term: Term<'a>) -> Result<Self, Error> {
443 Binary::from_term(term)
444 }
445}
446impl Encoder for Binary<'_> {
447 fn encode<'b>(&self, env: Env<'b>) -> Term<'b> {
448 self.to_term(env)
449 }
450}
451impl Hash for Binary<'_> {
452 fn hash<H: Hasher>(&self, state: &mut H) {
453 self.as_slice().hash(state);
454 }
455}
456impl PartialEq for Binary<'_> {
457 fn eq(&self, other: &Self) -> bool {
458 self.as_slice() == other.as_slice()
459 }
460}
461impl Eq for Binary<'_> {}
462impl PartialEq<OwnedBinary> for Binary<'_> {
463 fn eq(&self, other: &OwnedBinary) -> bool {
464 self.as_slice() == other.as_slice()
465 }
466}
467
468/// ## Binary terms
469impl<'a> Term<'a> {
470 pub fn into_binary(self) -> NifResult<Binary<'a>> {
471 Binary::from_term(self)
472 }
473}
474
475/// An newly-created, mutable Erlang binary.
476///
477/// See [module-level doc](index.html) for more information.
478pub struct NewBinary<'a> {
479 buf: *mut u8,
480 size: usize,
481 // safety: we must not expose `term` until it is no longer possible to get a
482 // &mut ref to `buf`.
483 term: Term<'a>,
484}
485
486impl<'a> NewBinary<'a> {
487 /// Allocates a new `NewBinary`
488 #[inline]
489 pub fn new(env: Env<'a>, size: usize) -> Self {
490 let (buf, term) = unsafe { new_binary(env, size) };
491 NewBinary { buf, term, size }
492 }
493 /// Extracts a slice containing the entire binary.
494 #[inline]
495 pub fn as_slice(&self) -> &[u8] {
496 unsafe { ::std::slice::from_raw_parts(self.buf, self.size) }
497 }
498
499 /// Extracts a mutable slice of the entire binary.
500 #[inline]
501 pub fn as_mut_slice(&mut self) -> &mut [u8] {
502 unsafe { ::std::slice::from_raw_parts_mut(self.buf, self.size) }
503 }
504}
505
506impl<'a> From<NewBinary<'a>> for Binary<'a> {
507 #[inline]
508 fn from(new_binary: NewBinary<'a>) -> Self {
509 Binary::from_term(new_binary.term).unwrap()
510 }
511}
512
513impl<'a> From<NewBinary<'a>> for Term<'a> {
514 #[inline]
515 fn from(new_binary: NewBinary<'a>) -> Self {
516 new_binary.term
517 }
518}
519
520impl Deref for NewBinary<'_> {
521 type Target = [u8];
522 #[inline]
523 fn deref(&self) -> &[u8] {
524 self.as_slice()
525 }
526}
527impl DerefMut for NewBinary<'_> {
528 #[inline]
529 fn deref_mut(&mut self) -> &mut [u8] {
530 self.as_mut_slice()
531 }
532}