【问题标题】:How to return a newly created struct as a reference? [duplicate]如何返回新创建的结构作为参考? [复制]
【发布时间】:2016-10-13 21:10:25
【问题描述】:

作为学习 Rust 的练习,我决定实现一个位向量库,灵感来自 std::vec::Vec 来提供哪些方法。

我有以下代码:

extern crate num;

use std::cmp::Eq;
use std::ops::{BitAnd,BitOrAssign,Index,Shl};
use num::{One,Zero,Unsigned,NumCast};

pub trait BitStorage: Sized + 
    BitAnd<Self, Output = Self> + 
    BitOrAssign<Self> + 
    Shl<Self, Output = Self> + 
    Eq + Zero + One + Unsigned + NumCast + Copy {}

impl<S> BitStorage for S where S: Sized + 
    BitAnd<S, Output = S> + 
    BitOrAssign<S> + 
    Shl<S, Output = S> + 
    Eq + Zero + One + Unsigned + NumCast + Copy {}

pub struct BitVector<S: BitStorage> {
    data: Vec<S>,
    capacity: usize,
    storage_size: usize
}

impl<S: BitStorage> BitVector<S> {
    pub fn with_capacity(capacity: usize) -> BitVector<S> {
        let storage_size = std::mem::size_of::<S>() * 8;
        let len = (capacity / storage_size) + 1;
        BitVector { 
            data: vec![S::zero(); len],
            capacity: capacity,
            storage_size: storage_size
        }
    }

    pub fn get(&self, index: usize) -> Option<bool> {
        match self.index_in_bounds(index) {
            true => Some(self.get_unchecked(index)),
            false => None
        }
    }

    pub fn set(&mut self, index: usize, value: bool) {
        self.panic_index_bounds(index);
        let (data_index, remainder) = self.compute_data_index_and_remainder(index);
        let value = if value { S::one() } else { S::zero() };
        self.data[data_index] |= value << remainder;
    }

    pub fn capacity(&self) -> usize {
        self.capacity
    }

    pub fn split_at(&self, index: usize) -> (&BitVector<S>, &BitVector<S>) {
        self.panic_index_not_on_storage_bound(index);
        let data_index = self.compute_data_index(index);
        let (capacity_left, capacity_right) = self.compute_capacities(index);
        let (data_left, data_right) = self.data.split_at(data_index);

        let left = BitVector {
            data: data_left.to_vec(),
            capacity: capacity_left,
            storage_size: self.storage_size
        };
        let right = BitVector {
            data: data_right.to_vec(),
            capacity: capacity_right,
            storage_size: self.storage_size
        };
        (&left, &right)
    }

    pub fn split_at_mut(&mut self, index: usize) -> (&mut BitVector<S>, &mut BitVector<S>) {
        self.panic_index_not_on_storage_bound(index);
        let data_index = self.compute_data_index(index);
        let (capacity_left, capacity_right) = self.compute_capacities(index);
        let (data_left, data_right) = self.data.split_at_mut(data_index);

        let mut left = BitVector {
            data: data_left.to_vec(),
            capacity: capacity_left,
            storage_size: self.storage_size
        };
        let mut right = BitVector {
            data: data_right.to_vec(),
            capacity: capacity_right,
            storage_size: self.storage_size
        };
        (&mut left, &mut right)
    }

    #[inline]
    fn get_unchecked(&self, index: usize) -> bool {
        let (data_index, remainder) = self.compute_data_index_and_remainder(index);
        (self.data[data_index] & (S::one() << remainder)) != S::zero()
    }

    #[inline]
    fn compute_data_index_and_remainder(&self, index: usize) -> (usize, S) {
        let data_index = self.compute_data_index(index);
        let remainder = self.compute_data_remainder(index);
        (data_index, remainder)
    }

    #[inline]
    fn compute_data_index(&self, index: usize) -> usize {
        index / self.storage_size
    }

    #[inline]
    fn compute_data_remainder(&self, index: usize) -> S {
        let remainder = index % self.storage_size;
        // we know that remainder is always smaller or equal to the size that S can hold
        // for example if S = u8 then remainder <= 2^8 - 1
        let remainder: S = num::cast(remainder).unwrap();
        remainder
    }

    #[inline]
    fn compute_capacities(&self, index_to_split: usize) -> (usize, usize) {
        (index_to_split, self.capacity - index_to_split)
    }

    #[inline]
    fn index_in_bounds(&self, index: usize) -> bool {
        index < self.capacity
    }

    #[inline]
    fn panic_index_bounds(&self, index: usize) {
        if !self.index_in_bounds(index) {
            panic!("Index out of bounds. Length = {}, Index = {}", self.capacity, index);
        }
    }

    #[inline]
    fn panic_index_not_on_storage_bound(&self, index: usize) {
        if index % self.storage_size != 0 {
            panic!("Index not on storage bound. Storage size = {}, Index = {}", self.storage_size, index);
        }
    }
}

static TRUE: bool = true;
static FALSE: bool = false;

macro_rules! bool_ref {
    ($cond:expr) => (if $cond { &TRUE } else { &FALSE })
}

impl<S: BitStorage> Index<usize> for BitVector<S> {
    type Output = bool;

    fn index(&self, index: usize) -> &bool {
        self.panic_index_bounds(index);
        bool_ref!(self.get_unchecked(index))
    }
}

编译器错误发生在split_atsplit_at_mut 方法中:它们基本上告诉我leftright 在这两种情况下都没有足够长的时间作为参考返回。我理解这一点,因为它们是在堆栈上创建的,然后我想将它们作为参考返回。

但是,由于我的设计灵感来自 std::vec::Vec,您可以看到 in the SliceExt trait 他们的定义如下:

#[stable(feature = "core", since = "1.6.0")]
fn split_at(&self, mid: usize) -> (&[Self::Item], &[Self::Item]);

#[stable(feature = "core", since = "1.6.0")]
fn split_at_mut(&mut self, mid: usize) -> (&mut [Self::Item], &mut [Self::Item]);

我认为这样做是为了方便最终用户,因为他们更愿意处理引用而不是框。

我想我可以通过将返回的位向量放入Box&lt;_&gt; 来修复我的错误,但是有没有办法将创建的结构作为参考返回?

作为一个额外的问题:如果我返回 (BitVector&lt;S&gt;, BitVector&lt;S&gt;),它确实有效,这样做有什么缺点?为什么SliceExt trait 不这样做?

【问题讨论】:

  • @Shepmaster 我看不出有一种方法可以缩短相关代码,同时仍保留问题的精神,即。它是一个位向量以及它与std::vec::VecSliceExt 特征的关系。
  • get, set, capacity 函数无关。删除参数、泛型类型。以this结束。

标签: reference rust


【解决方案1】:

如何返回一个新创建的结构体作为参考?

你不能。没有办法解决这个问题;这简直是​​不可能的。如您所说,如果它在堆栈上声明,则该值将被删除,并且任何引用都将失效。

那么是什么让Vec 与众不同?

Vec&lt;T&gt; 是切片 (&amp;[T]) 的拥有对应物。 Vec 有一个指向数据开头的指针、一个计数和一个容量,而切片只有指针和一个计数。两者都保证所有数据都是连续的。在伪 Rust 中,它们看起来像这样:

struct Vec<T> {
    data: *mut T,
    size: usize,
    capacity: usize,
}

struct Slice<'a, T> {
    data: *mut T,
    size: usize,
}

Vec::split_at 可以返回切片,因为它本质上包含一个切片。它不是创建东西并返回对它的引用,它只是指针和计数的副本。

如果您为您拥有的数据类型创建一个借用的对应物,那么您可以返回它。类似的东西

struct BitVector {
    data: Vec<u8>,
    capacity: usize,
    storage_size: usize
}

struct BitSlice<'a> {
    data: &'a [u8],
    storage_size: usize,
}

impl BitVector {
    fn with_capacity(capacity: usize) -> BitVector {
        let storage_size = std::mem::size_of::<u8>() * 8;
        let len = (capacity / storage_size) + 1;
        BitVector { 
            data: vec![0; len],
            capacity: capacity,
            storage_size: storage_size
        }
    }

    fn split_at<'a>(&'a self) -> (BitSlice<'a>, BitSlice<'a>) {
        let (data_left, data_right) = self.data.split_at(0);
        let left = BitSlice {
            data: data_left,
            storage_size: self.storage_size
        };
        let right = BitSlice {
            data: data_right,
            storage_size: self.storage_size
        };
        (left, right)
    }
}

fn main() {}

要遵循Vec 的主题,您可能希望将DerefDerefMut 转换为BitSlice,然后在BitSlice 上实现所有不改变容量的方法。

我认为这样做是为了方便最终用户,因为他们更愿意处理引用而不是框。

引用和框在使用现场应该大部分是透明的。主要原因是性能。 Box 是堆分配的。

我想我可以通过将返回的位向量放入 Box 来修复我的错误<_>

这不是一个好主意。您已经通过Vec 进行了堆分配,并且将其装箱会引入另一种间接方式和额外的堆使用。

如果我返回(BitVector&lt;S&gt;, BitVector&lt;S&gt;),它确实有效,这样做有什么缺点?为什么SliceExt trait 不这样做?

是的,在这里您将返回堆分配的结构。 返回这些没有缺点,只是执行分配的缺点。这就是SliceExt 不这样做的原因。

这是否也直接转化为 split_at_mut 变体?

是的。

struct BitSliceMut<'a> {
    data: &'a mut [u8],
    storage_size: usize,
}

fn split_at_mut<'a>(&'a mut self) -> (BitSliceMut<'a>, BitSliceMut<'a>) {
    let (data_left, data_right) = self.data.split_at_mut (0);
    let left = BitSliceMut {
        data: data_left,
        storage_size: self.storage_size
    };
    let right = BitSliceMut {
        data: data_right,
        storage_size: self.storage_size
    };
    (left, right)
}

这有助于指出&amp;T&amp;mut T不同的类型,并且行为方式不同。

不允许将 (mut BitSlice, mut BitSlice 作为返回类型。

返回 mut T: What's the difference in `mut` before a variable name and after the `:`? 没有意义。对于 BitSliceMut,可变性是包含类型 (&amp;mut [u8]) 的一个方面。

【讨论】:

  • 这是否也直接转化为split_at_mut 变体?因为这似乎是最有趣的情况,但不允许将(mut BitSlice&lt;'a&gt;, mut BitSlice&lt;'a&gt; 作为返回类型。
  • @skiwi 添加了更多内容。
  • 我想我现在开始明白了,谢谢你的解释。所以这真的是我能得到的最好的吗?因为切片是不行的,因为这种结构代表了一个不能表示为切片的位向量(因为即使 bool 使用 u8 作为支持)。
  • 在尝试实现这一点并失败后,我得出了以下(希望是暂时的)结论:不可能实现 DerefDerefMut 特征,因为您最终仍然尝试创建BitSlice resp BitSliceMut 并返回对堆栈上某些内容的引用不起作用。
【解决方案2】:

为什么标准库被“允许”通过引用返回的答案是,它不会在堆栈上分配任何东西。它返回对已分配内存的引用,这些内存的寿命足够长。

所以你基本上有两种选择:

  • 如果您在堆栈上分配内存,则必须将其作为值返回。 这包括 Box<_> 场景。你返回 Box,它有一个指向堆分配内存的指针,作为值。

  • 如果您不在堆栈上分配内存,您可以返回对已存在于内存中的结果的引用。

在 Rust 中,按值返回是有效的,因为值是移动的,而不是复制的。

【讨论】:

    猜你喜欢
    • 2020-07-25
    • 1970-01-01
    • 2021-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多