Safety of the slice lifetime extending

Well, it's a little complicated, but I'll try to make the problem clear enough. Please feel free to ask if something doesn't make sense.

I'm trying to make some kind of "type-erased generic callbacks", with the following logic:

  • slice is deserialized into some type In,
  • user-provided function converts In to Out,
  • Out is serialized to bytes.

The first attempt, without (de)serialization, only with conversion logic, was here. Of course, it's possible to do this all based on Box<dyn Fn> (or some other smart pointer), but I'd like to explore other possibilities.

First of all, here's a sketch of the working code with similar functionality:

use serde::de::Deserialize;
use serde::Serialize;
use serde_json::from_slice;
use serde_json::to_vec;

// This structure is provided by library user,
// types In and Out are also chosen by them.
struct Action<In, Out>(fn(In) -> Out);

impl<In, Out> Action<In, Out> {
    // Action is a three-step process:
    fn act<'a>(&self, data: &'a [u8]) -> Vec<u8>
    where In: Deserialize<'a>, Out: Serialize
    {
        // Deserializing data from slice...
        let data: In = from_slice(data).unwrap();
        // ...processing it...
        let data: Out = (self.0)(data);
        // ...serializing result to vector
        let data: Vec<u8> = to_vec(&data).unwrap();
        data
    }
}

fn str_identity(s: &str) -> &str {
    s
}

fn main() {
    let mut v = b"\"Hello bytes world!\"".to_vec();
    let action = Action(str_identity);
    let processed = action.act(&v);
    println!("{}", String::from_utf8(processed).unwrap());
    v[1] = b'Y';
    println!("{}", String::from_utf8(v).unwrap());
}

Playground

However, in real code I have to make Action non-generic, in other words, to make its type independent of In and Out, so that these actions can be stored in the same way (for example, in the same Vec). Here's the attempt I've done for now:

use serde::de::Deserialize;
use serde::Serialize;
use serde_json::from_slice;
use serde_json::to_vec;

struct Action {
    wrapper: for<'a> fn(&'a Self, &'a [u8]) -> Vec<u8>,
    // This pointer is, in fact, the function pointer of kind `fn(T) -> U`.
    action: *const (),
}

impl Action {
    fn new<'a, In: Deserialize<'a>, Out: Serialize>(action: fn(In) -> Out) -> Action {
        Action {
            wrapper: Action::wrapper::<In, Out>,
            action: action as *const (),
        }
    }
    
    // I can't here accept `data: &'a [u8]`, since this will conflict with
    // setting the `wrapper` field in `new`
    fn wrapper<'a, In: Deserialize<'a>, Out: Serialize>(this: &Self, data: &[u8]) -> Vec<u8> {
        // commenting out this line triggers error
        let data: &'a [u8] = unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
        let data: In = from_slice(data).unwrap();
        // Safety: `this.action` can only be cast from `fn(In) -> Out`
        let action: fn(In) -> Out = unsafe { std::mem::transmute(this.action) };
        let data: Out = action(data);
        let data: Vec<u8> = to_vec(&data).unwrap();
        data
    }

    fn act<'a>(&self, data: &'a [u8]) -> Vec<u8> {
        (self.wrapper)(self, data)
    }
}

fn str_identity(s: &str) -> &str {
    s
}

fn main() {
    let mut v = b"\"Hello bytes world!\"".to_vec();
    let action = Action::new(str_identity);
    let processed = action.act(&v);
    println!("{}", String::from_utf8(processed).unwrap());
    v[1] = b'Y';
    println!("{}", String::from_utf8(v).unwrap());
}

Playground

The line I'm asking for in this topic is the first one inside wrapper:

let data: &'a [u8] = unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };

The problem is, as far as I can tell, is the following:

  • When creating function pointer to fill the wrapper field, I have to set the fixed type.
  • However, in different calls to wrapper I will deserialize provided slices into possibly different types, because they will have different lifetime parameters - bound to the lifetime of the passed-in slice.

If I understand correctly, this might be possible to do with GATs (by making In the generic associated type of some trait and binding the trait's lifetime to the slice), but, again, I'm exploring the stable features for now. Is there any more clear workaround?