0
votes

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. :(

1
I believe your question is answered by the answers of How to create a DST type?. If you disagree, please edit your question to explain the differences. Otherwise, we can mark this question as already answered.Shepmaster
This question is about how to create your own "kind" of DST type, since there are only two "kind"s of DSTs in Rust: Arrays and trait objects. These base DST types can be used to create new "derived" DSTs. I don't want to create a new "kind" of DST, I want to create a pointer to an array-based DST without using unsafe code. I will edit my question to clear this up.Janonard
C doesn't have fat pointer or DST so you will have to precise what your hypothetical C API is doing. I check a little on drobilla.net/docs/lilv doesn't give me much information. Your Atom structure is not C compatible see stackoverflow.com/a/53050526/1233251. So, If you want to do Rust-to-C, your question don't make sense.Stargateur
Do you actually need a fat pointer to manipulate this in Rust code? If the value is always constructed outside of Rust, then can't you just refer to it via a normal reference, &Atom, assuming you can make guarantees about its lifetime or a *const Atom if not?Peter Hall

1 Answers

-1
votes

Assuming you want dynamic-sized-types

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:

Assuming you want LV2 atoms

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.