1

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

Janonard
  • 45
  • 4
  • I believe your question is answered by the answers of [How to create a DST type?](https://stackoverflow.com/questions/30174822/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 Apr 03 '19 at 13:49
  • 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 Apr 03 '19 at 14:00
  • Then perhaps [How do you actually use dynamically sized types in Rust?](https://stackoverflow.com/q/25740916/155423) is relevant. – Shepmaster Apr 03 '19 at 14:02
  • Does `Atom` actually have other (sized) fields besides `data` and `len`? – Peter Hall Apr 03 '19 at 14:06
  • 1
    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 http://drobilla.net/docs/lilv/ doesn't give me much information. Your `Atom` structure is not C compatible see https://stackoverflow.com/a/53050526/1233251. So, If you want to do Rust-to-C, your question don't make sense. – Stargateur Apr 03 '19 at 14:21
  • 1
    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 Apr 03 '19 at 14:23
  • Perhaps also via an opaque pointer: [What's the Rust idiom to define a field pointing to a C opaque pointer?](https://stackoverflow.com/q/38315383/155423) – Shepmaster Apr 03 '19 at 14:24
  • Sorry for not testing my code before posting, I should have done that. Also: Yes, C can't express DSTs, but No, Atoms are dynamically sized types: The general idea of an atom is that it has a sized header in the beginning and then an arbitrary, _dynamic_ amount of bytes followed. Also, I have a good argument for this model being compatible with C: It works in my project! ([project](https://github.com/Janonard/lv2rs.git), [examples](https://github.com/Janonard/lv2rs-book.git) ) – Janonard Apr 03 '19 at 14:43
  • "Everything works just fine, I've deeply thought about the data models and tested it;" I really doubt about that. looking by your code what you do is incorrect – Stargateur Apr 03 '19 at 15:02
  • Please, keep update this question as you go though your project but I think you should look at FAM in C and how bindgen [handle](https://stackoverflow.com/a/53055574/7076153) them (you should do something similar), also, forget about the idea to create the fat pointer yourself in Rust that will not help you to make better code. – Stargateur Apr 03 '19 at 16:15
  • See also [Is it possible to have stack allocated arrays with the size determined at runtime in Rust?](https://stackoverflow.com/q/27859822/155423) – Shepmaster Apr 03 '19 at 23:00
  • "forget about the idea to create the fat pointer yourself in Rust" Why should I forget about fat pointers? Could you explain that? Is it because the raw representation of fat pointer isn't stable across versions or architectures? I mean, slices are fat pointers too and the `from_raw_parts` method also constructs them in a similar way (It uses a union instead of `transmute`, but it's basically the same). The way `from_raw_parts` handles fat pointers even inspired me to use this approach. – Janonard Apr 04 '19 at 10:45

1 Answers1

-1

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 2
    I'm sorry, but that doesn't solve the problem. The problem is about how to safely create a fat pointer to an array-based type, not if LV2 atoms are DSTs or not. I shouldn't have said something about LV2, it made the discussion go in a completely wrong direction. Everything works fine, I just want to have a safe alternative to `transmute` :( – Janonard Apr 03 '19 at 14:54