Reputation: 45
I am working on an integration of LV2 atoms for Rust, which are slice-based dynamically sized types (DSTs). In general, atoms are created by the host or other plugins and my code receives only a thin pointer to them. Therefore, I need to create a fat pointer to a slice-based DST from a thin pointer. This is how my code roughly looks like:
#[repr(C)]
struct Atom {
/// The len of the `data` field.
len: u32,
/// The data.
data: [u8],
}
/// This is the host. It creates the atom in a generally unknown way and calls
/// the plugin's run function. This isn't really part of my code, but it is
/// needed to understand it.
fn host() {
// The raw representation of the atom
let raw_representation: [u8; 8] = [
// len: Raw representation of a `u32`. We have four data bytes.
4, 0, 0, 0,
// The actual data:
1, 2, 3, 4
];
let ptr: *const u8 = raw_representation.as_ptr();
plugin_run(ptr);
}
/// This function represents the plugin's run function:
/// It only knows the pointer to the atom, nothing more.
fn plugin_run(ptr: *const u8) {
// The length of the data.
let len: u32 = *unsafe { (ptr as *const u32).as_ref() }.unwrap();
// The "true" representation of the fat pointer.
let fat_pointer: (*const u8, usize) = (ptr, len as usize);
// transmuting the tuple into the actuall raw pointer.
let atom: *const Atom = unsafe { std::mem::transmute(fat_pointer) };
println!("{:?}", &atom.data);
}
The interesting line is the penultimate line in plugin_run
: Here, a fat pointer is created by transmuting a tuple containing the thin pointer and the length. This approach works, but it uses the highly unsafe transmute
method. According to the documentation, this method should be the absolute last resort, but as far as I understand the design of Rust, I'm doing nothing that should be unsafe. I'm just creating a pointer!
I don't want to stick with this solution. Is there a safe way to create pointers to array-based structs apart from using transmute
?
The only thing that goes in this direction is std::slice::from_raw_parts
, but it is also unsafe, directly creates a reference, not a pointer, and works exclusively for slices. I found nothing in the standard library, I found nothing on crates.io, I even found no issues in the git repo. Am I searching for the wrong keywords? Is something like this actually welcome in Rust?
If nothing like this exists, I would go forth and create an issue for that in Rust's Github repo.
EDIT: I shouldn't have said something about LV2, it made the discussion go in a completely different direction. Everything works just fine, I've deeply thought about the data models and tested it; The only thing I would like to have is a safe alternative to using transmute
to create fat pointers. :(
Upvotes: 1
Views: 1230
Reputation: 431569
Change your type to a generic one that allows an unsized parameter that defaults to [u8]
. Then you can take a concrete value and get the unsized version:
#[repr(C)]
struct Atom<D: ?Sized = [u8]> {
/// The len of the `data` field.
len: u32,
/// The data.
data: D,
}
fn main() {
let a1: &Atom = &Atom {
len: 12,
data: [1, 2, 3],
};
let a2: Box<Atom> = Box::new(Atom {
len: 34,
data: [4, 5, 6],
});
}
See also:
I am working on an integration of LV2 atoms for Rust
Is there a reason you are not using the existing crate that deals with this?
LV2 atoms for Rust, which are slice-based dynamically sized types
They are not, according to the source code I could find. Instead, it only uses structural composition:
typedef struct {
uint32_t size; /**< Size in bytes, not including type and size. */
uint32_t type; /**< Type of this atom (mapped URI). */
} LV2_Atom;
/** An atom:Int or atom:Bool. May be cast to LV2_Atom. */
typedef struct {
LV2_Atom atom; /**< Atom header. */
int32_t body; /**< Integer value. */
} LV2_Atom_Int;
The official documentation appears to agree. This means that it would likely be invalid to ever take an LV2_Atom
by value as you'd only copy the header, not the body. This is commonly referred to as struct tearing.
In Rust, this could be implemented as:
use libc::{int32_t, uint32_t};
#[repr(C)]
struct Atom {
size: uint32_t,
r#type: uint32_t,
}
#[repr(C)]
struct Int {
atom: Atom,
body: int32_t,
}
const INT_TYPE: uint32_t = 42; // Not real
extern "C" {
fn get_some_atom() -> *const Atom;
}
fn get_int() -> Option<*const Int> {
unsafe {
let a = get_some_atom();
if (*a).r#type == INT_TYPE {
Some(a as *const Int)
} else {
None
}
}
}
fn use_int() {
if let Some(i) = get_int() {
let val = unsafe { (*i).body };
println!("{}", val);
}
}
If you look at the source code for the lv2_raw crate, you'll see much the same thing. There's also a lv2 crate that attempts to bring a higher-level interface.
Upvotes: -1