How to make a Rc for a slice?

I want a struct has a piece of data and a pointer points to a slice of it, so I wrote:

use std::ops::Deref;
use std::rc::Rc;
use std::ptr::NonNull;
use std::pin::Pin;

pub struct SharedSlice{
    template:Pin<Rc<Vec<u8>>>,
    slice:NonNull<[u8]>
}

impl SharedSlice{
    fn new(data:Vec<u8>)->Self{
        let rc=Rc::pin(data);
        let ptr=NonNull::from(&**rc);

        SharedSlice{
            template:rc,
            slice:ptr
        }
    }

    fn new_slice<'a>(&'a self,data:&'a [u8])->SharedSlice{
        SharedSlice{
            template:self.template.clone(),
            slice:NonNull::from(data)
        }
    }
}

impl Deref for SharedSlice{
    type Target = [u8];

    fn deref(&self)->&[u8]{
        unsafe{ self.slice.as_ref() }
    }
}

Then we could use it like this, and it works well.

fn main() {
    let ss1=SharedSlice::new(vec![1,2,3]);
    let ss2=ss1.new_slice(&ss1[..2]);
    println!("{:?}",&*ss2);
}

But I tested the following code, unexpectedly found it could pass the compiling:

fn main() {
    let ss1=SharedSlice::new(vec![1,2,3]);
    let ss2=ss1.new_slice(&vec![5,6,7]);
    println!("{:?}",&*ss2);
}

As expected, the result is a set of wrong data. So how can I prevent the second case from passing compile checking?

I'd rather use the approach below and get rid of all the unsafes for this case.

pub struct SharedSlice{
    template: Rc<[u8]>,
    start: usize,
    end: usize,
}
1 Like

But I need to modify the slice directly, such as calling read and split_first on it. In this case I don't know the range of the modified slice in raw data.

FYI, pinning a vec most likely doesn't have the effect you indended.


I'd second the advice to spend some effort on avoiding unsafe; it's not woth the hassle to deal with the potential effects of unexpected UB. Try not to use unsafe, especially when you're not that familiar with the more subtile corners of Rust yet; don't use unsafe if there's any equivalently good safe alternative, a safe abstraction in a popular crate, or even a slightly worse (performance-wise) safe alternative (when your code isn't performance-critical).

There was a similar question here. Using those ideas, this would be correct.

1 Like

You can get a subslice's range in the original slice like this:

use std::ops::Range;

fn get_indexes(big_slice: &[u8], subslice: &[u8]) -> Range<usize> {
    let range_start = subslice.as_ptr() as usize;
    let buffer_start = big_slice.as_ptr() as usize;
    
    let offset = range_start - buffer_start;
    
    let range = offset .. offset+subslice.len();
    assert!(big_slice.get(range.clone()).is_some());
    range
}

fn main() {
    let slice = &[1, 2, 3, 4];
    let subslice = &slice[1..2];
    
    println!("{:?}", get_indexes(slice, subslice));
}

split_first doesn't modify the slice. read takes &mut &[u8], reading on it obtained from the Deref impl doesn't magically modify the backing SharedSlice directly. If you want to make two sequential .read() call from the same SharedSlice yield different data, you need to impl Read for SharedSlice anyway.

But in this way, if I give two unrelated &[u8], the result is unexpected.

get_indexes(&[1,2,3,4],&[6,7,8,9]) // The result doesn't make sense

Yes I know that, it's my fault I didn't speak it correctly. I mean, I want to replace the old data with modified data. In other words, I need replace the original slice with its subslice.

If you give it two unrelated slices, what you get is a panic.

But I have no idea how to avoid using unsafe :rofl:
As far as I know, it's unavoidably to using unsafe in self referenced struct.

Not necessarily with

e.g. take a look at https://crates.io/crates/ouroboros

example code for your case (click to expand)
use std::ops::Deref;
use std::rc::Rc;

use ouroboros::self_referencing;

#[self_referencing]
struct SharedSliceInternals {
    template: Rc<Vec<u8>>,
    #[borrows(template)]
    slice: &'this [u8],
}

pub struct SharedSlice(SharedSliceInternals);

impl SharedSlice {
    pub fn new(data: Vec<u8>) -> Self {
        Self({
            SharedSliceInternalsBuilder {
                template: Rc::new(data),
                slice_builder: |template| template,
            }
            .build()
        })
    }

    pub fn new_slice(&self, data: &[u8]) -> Option<SharedSlice> {
        let template = self.0.borrow_template();
        let template_start = template as &[u8] as *const [u8] as *const u8 as usize;
        let slice_start = data as &[u8] as *const [u8] as *const u8 as usize;
        if slice_start < template_start {
            return None;
        }
        let offset = slice_start - template_start;
        dbg!(offset);
        if template.len() < offset + data.len() {
            return None;
        }
        let range = offset..offset + data.len();

        Some(Self({
            SharedSliceInternalsBuilder {
                template: Rc::clone(template),
                slice_builder: |template| &template[range],
            }
            .build()
        }))
    }

    // can be used e.g. to call `.read()` on the slice
    pub fn with_mut<'a, F, R>(&'a mut self, f: F) -> R
    where
        F: FnOnce(&'a mut &[u8]) -> R,
    {
        self.0.with_slice_mut(f)
    }
}

impl Deref for SharedSlice {
    type Target = [u8];

    fn deref(&self) -> &[u8] {
        self.0.borrow_slice()
    }
}

fn main() {
    let x = vec![1, 2, 3, 4, 5, 6, 7];
    let mut s = SharedSlice::new(x);
    let mut t = s.new_slice(&s[2..]).unwrap();

    let mut buf = vec![];

    use std::io::Read;

    s.with_mut(|s| s.read_to_end(&mut buf)).unwrap();
    buf.push(0);
    t.with_mut(|t| t.read_to_end(&mut buf)).unwrap();

    assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 0, 3, 4, 5, 6, 7]);
}

actually, after writing this, I realize that (almost1) the same API should be possible with a non-self-referencing index-based approach, too, in particular a function like with_mut. You just create a slice on the stack, pass a mutable reference to it to the callback, and after the callback finishes, you update the indices accordingly.

1only difference would be that that approach could never store any slices, e.g. from a &'static [u8] literal, that don't point into the Rc-owned data anymore, while the code above supports such a modification. (It would lead to subsequent as_slice(&self[....]) calls returning None though.)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.