20 releases
0.5.4 | Oct 7, 2023 |
---|---|
0.5.0 | Mar 17, 2023 |
0.3.4 | Dec 17, 2022 |
0.3.3 | Oct 16, 2022 |
0.1.2 | Jul 24, 2015 |
#59 in FFI
56 downloads per month
72KB
1.5K
SLoC
Sentinel
sentinel
is a sentinel-terminated slice library.
How it works
Rust Slices
In Rust, the slice type &[T]
is basically defined like that: (*const T, usize)
. The usize
indicates the number of T
s referenced at the *const T
. Knowing in advance the size of an array,
like that, has numerous advantages, which won't be discussed here.
There is however two main problems with the &[T]
type:
-
It is not (at least, yet) FFI-safe. One cannot create an
extern "C" fn(s: &[u32])
function and expect it to work when calling it from C-code. -
The size of
&[T]
has the size of twousize
s.
Sentinels?
A sentinel is a special value that is used to determine the end of an array. For example, in C, the
char *
type can be a pointer to a "null-terminated" string. This is an example of
sentinel-terminated slice.
CString:
char *ptr
|
'H' 'e' 'l' 'l' 'o' '\0'
^ sentinel, anything after this point may be invalid.
str:
*const u8, 5
|
'H' 'e' 'l' 'l' 'o'
^ no sentinel, we know the slice contains 5 elements.
This crate remains generic over how sentinels are defined. It uses the Sentinel
trait, which is
roughly defined like that:
trait Sentinel<T> {
fn is_sentinel(val: &T) -> bool;
}
It is used to determine whether a specific instance of T
should be treated as a "sentinel" value.
SSlice
Finally, in conjonction with the Sentinel
trait, this crate defines the SSlice<T>
type.
It is generic over T
, the type of stored elements, overing great flexibility.
struct SSlice<T> {
_marker: PhantomData<T>,
}
Note that this type actually contains no data. Only references to this type can be created (i.e.
&SSlice<T>
or &mut SSlice<T>
), and those references have the size a single usize
.
FFI
The SSlice<T>
type is FFI safe, which mean you can now write this:
// type CStr = sentinel::SSlice<u8>;
extern "C" {
/// # Safety
///
/// This will be `unsafe` because of `extern "C"`. But calling libc's `puts` with this
/// signature is always sound!
fn puts(s: &sentinel::CStr);
}
Or this!
extern crate libc;
use sentinel::{cstr, CStr, SSlice};
fn print(s: &CStr) {
// SAFETY:
// `CStr` ensures that the string is null-terminated.
unsafe { libc::puts(s.as_ptr() as _) };
}
#[no_mangle]
extern "C" fn main(_ac: libc::c_int, argv: &SSlice<Option<&CStr>>) -> libc::c_int {
print(cstr!("Arguments:"));
for arg in argv.iter().unwrap_sentinels() {
print(arg);
}
0
}
Features
-
alloc
- adds support for thealloc
crate. This adds theSBox<T>
type. -
nightly
- makes use of the unstableextern_type
feature to make sure no instance ofSSlice<T>
can be created on the stack by making it!Sized
. This feature also enables support for the newallocator_api
unstable feature. -
libc
- use the libc'sstrlen
andmemchr
to look for null characters in sentinel-terminated slices. -
memchr
- use thememchr
crate to look for null characters in sentinel-terminated slices.
alloc
and memchr
are enabled by default.
Old sentinel
crate
The name sentinel
was kindly given to me by the previous maintainer of this project.
Every pre-0.2 versions (on crates.io) contain the source code of that crate.
Dependencies
~105–260KB