2016-06-13 73 views
6

作爲一個學習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<_>來解決我的錯誤,但是有沒有辦法將創建的結構作爲參考返回?

作爲一個紅利問題:如果我返回(BitVector<S>, BitVector<S>),那麼它會工作,這樣做的缺點是什麼?爲什麼SliceExt特質不這樣做?

+0

請製作[MCVE]。 – Shepmaster

+4

@Shepmaster我沒有看到縮短代碼的方法,同時仍然保留問題的精神,即。它是一個向量,它與'std :: vec :: Vec'和'SliceExt'特徵有什麼關係。 – skiwi

+1

'get','set','capacity'函數是無關緊要的。刪除參數,泛型類型。結束[this](https://play.rust-lang.org/?gist=aa638e102672e09ebcce3098762cf947&version=stable&backtrace=0)。 – Shepmaster

回答

5

如何返回一個新創建的結構作爲參考?

你不行。沒辦法;這根本不可能。正如你所說,如果它在堆棧中聲明,那麼該值將被刪除,並且任何引用都將失效。

那麼是什麼使Vec不同?

A Vec<T>是分片的所有者(&[T])。雖然Vec具有指向數據開始,計數和容量的指針,但片只有指針和計數。兩者都保證所有數據都是連續的。在僞鏽,他們是這樣的:

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主題,你會想可能DerefDerefMutBitSlice,然後實現所有非容量變化的BitSlice方法。

我想這是爲了最終用戶的方便,因爲他們寧願處理引用而不是框。

參考文獻和方框在使用地點應該大多是透明的。主要原因是性能。 A Box是堆分配的。

我想我可以通過將返回位向量成箱< _>

這不會是一個好主意,解決我的錯誤。你已經通過Vec獲得了堆分配,並且它會引入另一個間接和額外的堆使用。

如果我返回(BitVector<S>, BitVector<S>),確實有效,那麼這樣做有什麼缺點?爲什麼SliceExt特質不這樣做?

是的,在這裏你正在返回堆分配結構。 沒有任何缺點返回這些,這只是執行分配的缺點。這就是爲什麼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) 
} 

這有助於指出,&T&mut T不同類型並以不同的方式表現。

它不允許給(MUT BitSlice < '一>,MUT BitSlice <' 一>的返回類型

這是沒有意義的返回mut T:。What's the difference in `mut` before a variable name and after the `:`?隨着BitSliceMut,可變性是包含類型的一個方面(&mut [u8]

+0

這是否也直接轉化爲'split_at_mut'變體?因爲這似乎是最有趣的情況,但它不允許給'(mut BitSlice <'a>,mut BitSlice <'a>'作爲返回類型。 – skiwi

+0

@skiwi添加了更多 – Shepmaster

+0

我想我現在開始明白它了,謝謝所以這真的是我能得到的最好的結果因爲切片是不可行的,因爲這個結構代表了一個不能表示爲切片的位向量(因爲即使'bool'使用'u8'作爲後盾)。 – skiwi

1

爲什麼標準庫被'允許'通過引用返回的答案是,它沒有在棧上分配任何東西,它返回對已經分配這個記憶足夠長。

所以,你有兩種基本的選擇:

  • 如果你必須把它作爲返回值在棧上分配內存。 這包括方框< _>方案。您返回Box,它有一個指向堆分配內存的指針,作爲值。

  • 如果你沒有在棧上分配內存,你可以返回已經存在於內存中的結果的引用。

在Rust中,按值返回是有效的,因爲該值被移動,而不是被複制。

相關問題