I recently ran into an issue where I wanted to use Any for slices. However, it only allows 'static types (based on what I read, this is because you get the same TypeId regardless of lifetimes).

I came up with this workaround which I think is safe:

use std::{
    any::{Any, TypeId},
    marker::PhantomData,
};

#[derive(Clone, Debug)]
pub struct AnySlice<'a> {
    tid: TypeId,
    len: usize,
    ptr: *const (),
    marker: PhantomData<&'a ()>,
}

impl<'a> AnySlice<'a> {
    pub fn from_slice(s: &'a [T]) -> Self {
        Self {
            len: s.len(),
            ptr: s.as_ptr() as *const (),
            tid: TypeId::of::(),
            marker: PhantomData,
        }
    }

    pub fn as_slice(&self) -> Option<&'a [T]> {
        if TypeId::of::() != self.tid {
            return None;
        }
        Some(unsafe { std::slice::from_raw_parts(self.ptr as *const T, self.len) })
    }

    pub fn is(&self) -> bool {
        TypeId::of::() == self.tid
    }
}

edit: Unfortunately it seems like Lemmy insists on mangling the code block. See the playground link below.

T: Any ensures T is also 'static. The lifetime is preserved with PhantomData. Here’s a playground link with some simple tests and a mut version: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3116a404c28317c46dbba6ed6824c8a9

It seems to pass Miri, including the mut version (which requires a bit more care to ensure there can only be one mutable reference). Any problems with doing this?

  • Kerfuffle@sh.itjust.worksOP
    link
    fedilink
    English
    arrow-up
    2
    ·
    1 year ago

    The more I think about it, the more I’m convinced the immutable slice version is safe. I’m pretty sure the mutable version in the playground is also safe because you can’t turn it into a mutable reference without consuming it and it requires a mutable reference to build.

    The mutable version is pretty inconvenient to actually use if you want to store the AnySliceMut and pass it to other functions. The other function would have to reconstruct it and pass it back once it was done with the mutable reference. But what if:

    // ^ impl AnySliceMut
        pub unsafe fn as_slice_mut(&mut self) -> Option<&'a mut [T]> {
            if TypeId::of::() != self.tid {
                return None;
            }
            Some(unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut T, self.len) })
        }
    

    Obviously it’s possible to abuse but if I just do something like pass it to a function that takes a mut ref and that function doesn’t do anything weird like save the reference is this okay? MIRI is apparently okay with it: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=149ad441a1c66b3f1fd7f2107acbeccf

    • Kerfuffle@sh.itjust.worksOP
      link
      fedilink
      English
      arrow-up
      2
      ·
      1 year ago

      Thanks for the reply. Unfortunately, I could just use Any for that since Vec is 'static as long as the items are (and I don’t think you could use anyvec if the tiems weren’t static).

      I asked in the Rust Discord channels and it seems like my approach is fine. Even the mutable version is okay and doesn’t need to be unsafe if it returns the reference with self’s lifetime.

      • _Vi@lemmyrs.org
        link
        fedilink
        English
        arrow-up
        3
        ·
        1 year ago

        I asked in the Rust Discord channels and it seems like my approach is fine. Even the mutable version is okay and doesn’t need to be unsafe if it returns the reference with self’s lifetime.

        Then maybe publish it as a mini-crate (e.g. any-slice), ideally with a link to the discussion that proves that it is sound.

  • _Vi@lemmyrs.org
    link
    fedilink
    English
    arrow-up
    1
    ·
    edit-2
    1 year ago

    Note: both code fragments and links contain things like & which makes them inoperable as is.

    • Kerfuffle@sh.itjust.worksOP
      link
      fedilink
      English
      arrow-up
      2
      ·
      1 year ago

      Note: both code fragments and links contain things like &

      Yes, I know. lemmy mangles stuff like ampersand and less than even inside code blocks. I noted this in an edit for the main post and included a playground link as well.