2

As part of mapping a C interface to Rust, I want to handle a union that stored a few native types directly and have a pointer to an allocated type for all others.

How can I implement a parameterized wrapper type around the union that can select and use the appropriate field based on the wrapper type parameter?

In this case, I want to add a Rust wrapper that reads the data structure directly and not a solution that converts it to a Rust-native type first. Adding other support types to "trick" the compiler is fine though. The end goal is to be able to write code similar to this:

let the_list: List<i32> = get_a_list();
for i in the_list {
    println!("value")
}

I am skipping the definitions of IntoIterator and friends since that boils down to accessing the correct field based on the type.

The code is highly unsafe, but we can assume that the user provides the correct type for the type parameter.

There are other solutions to this problem such as having explicit reader functions for each type, but this is focused on understanding if there are ways to make it work without those and without introducing an undue overhead.

Code that does not work, but illustrates what I want to accomplish:

#![feature(specialization)]
use std::convert::From;

union Data<T> {
    int_value: i64,
    ptr_value: *const T,
}

default impl<T> From<&Data<T>> for &T {
    fn from(data: &Data<T>) -> &T {
        &*data.ptr_value
    }
}

impl From<&Data<i64>> for &i64 {
    fn from(data: &Data<i64>) -> &i64 {
        &*data.int_value
    }
}

fn show<T>(value: &T) {
    println!("value: {}", value);
}

fn main() {
    let value = String::from("magic");
    let data: Data<&str> = Data {
        ptr_value: value.as_ptr(),
    };
    show(data.into());
}

I have minimized the example to avoid discussions about other aspects. This gives the following error:

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
  --> examples/union_convert.rs:10:14
   |
10 | default impl<T> From<&Data<T>> for &T {
   |              ^ type parameter `T` must be used as the type parameter for some local type
   |
   = note: implementing a foreign trait is only possible if at least one of the types for which is it implemented is local
   = note: only traits defined in the current crate can be implemented for a type parameter

I have tried adding a wrapper around the union to handle the type-punning, but that seems to just push the error message around. Returning some other type than &T would also be possible, but I do not understand how to make it behave correctly. Using a different return type is also an option, but it still boils down to selecting the correct field based on a type.

Looking at the implementation of std::vec::Vec it does a similar thing, but in this case it always map the memory representation of the type to a real type. In this case, I want to select the correct union field based on the type that was used when writing the value.

Other similar questions that do not really answer this questions:

Update: Provided a more distinct example of using into() to convert the union type one of the fields.

Mats Kindahl
  • 1,863
  • 14
  • 25
  • 1
    It seems you missed some code at the first snippet, may you check it? – Kitsu Jun 22 '20 at 13:32
  • That is just an example. I use the FFI to create the actual data structure, so there is nothing additional to add. Second code example is what I would like to make it work. Making the first snippet work would require `IntoIterator` and some iterator proxy, which makes the question more complicated than necessary. – Mats Kindahl Jun 22 '20 at 13:42
  • Thanks for pointing me to the question, I had missed adding it, but I do not think that it answers the question since it goes in the other direction (`T -> Data` instead of `Data -> T&`). Not sure what edits you would like to see, but I added a reference to the question and explained why I think it does not answer the question. – Mats Kindahl Jun 22 '20 at 16:21
  • It was an attempt at resolving the issue by providing a default implementation. Other solutions are welcome as well, but the key point is that if an `i64` is requested, the `int_value` field should be read and provide a reference to the value, in all other cases, the `ptr_value` field should be read and provide a reference to the object. – Mats Kindahl Jun 22 '20 at 16:31
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/216445/discussion-between-mats-kindahl-and-shepmaster). – Mats Kindahl Jun 22 '20 at 16:39

1 Answers1

3

I would define my own trait instead of From to avoid conflicting with the standard library implementations. I'd also define a newtype wrapper / marker type. This removes the possibility of conflict when storing one of the specific types in the generic spot.

struct Other<T>(T);

union Data<T> {
    bool_value: bool,
    int_value: i64,
    ptr_value: *const T,
}

trait Access<T> {
    type Output;

    unsafe fn access(&self) -> &Self::Output;
}

impl<T> Access<Other<T>> for Data<T> {
    type Output = T;

    unsafe fn access(&self) -> &Self::Output {
        &*self.ptr_value
    }
}

impl<T> Access<bool> for Data<T> {
    type Output = bool;

    unsafe fn access(&self) -> &Self::Output {
        &self.bool_value
    }
}

impl<T> Access<i64> for Data<T> {
    type Output = i64;

    unsafe fn access(&self) -> &Self::Output {
        &self.int_value
    }
}

fn main() {
    let value = 123_f64;
    let data: Data<f64> = Data { ptr_value: &value };

    // This is safe because we just created this with
    // a `f64` and nothing happened in-between.
    unsafe {
        println!("{}", Access::<Other<f64>>::access(&data));
    }
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I tested to see if I could put an iterator interface on top of a external (non-Rust) representation of a list, and it seems to work fine. – Mats Kindahl Jun 24 '20 at 19:19