我有一个小库,可以在其中定义整数类型。这些是为了在我经常编写的算法中对数组和字符串进行类型安全索引。例如,我可以用它来定义偏移类型Offset
和索引类型Idx
,这样你可以通过减去两个Idx
得到Offset
,通过加或减Offset
得到Idx
,但你不能例如多重或相加Idx
。
let (i,j): (Idx,Idx) = ...;
let offset: Offset = j - i; // Subtracting indices gives an offset
let k: Idx = j + offset; // Adding an offset to an index gives an index
// let _ = i + j; -- You can't add indices
我设法(有一些困难)实现了std::iter::Step
,这样我也可以迭代一系列索引
for k in i .. j { /* ... */ }
但现在我把目光投向了一个更高的目标:我还想使用这些类型的范围来划分序列
let v: Vec<sometype> = vec![...];
let w: &[sometype] = &v[i..j]; // Slice with a range of Idx
这应该是实现std::ops::Index
和std::ops::IndexMut
的简单问题,除了类型系统不允许我实现
impl<I,T> std::ops::Index<std::ops::Range<I>> for Vec<T>
where I: /* my types */
{ ... }
对于包装器类型或通用
impl<I,T> std::ops::Index<std::ops::Range<Wrapper<I>>> for Vec<T>
where I: /* my types */
{ ... }
其中Wrapper
是我实际使用的类型,I
是帮助我编写泛型代码的特性。
问题是Index
和Range
都是在我的机箱之外定义的,所以这种专业化是不允许的。还是这样?
当泛型类型的泛型参数来自我的机箱时,我有没有办法在机箱外实现它的特性?
或者,更好的是,有没有办法利用..
运算符的语法糖,这样我就可以得到一个包装类型?我真正想要的是包装Range
以获得相同的行为,然后是一些行为。我可以通过包装Range
并实现Deref
来做到这一点,但如果我走这条路,我就会失去..
运算符的语法优势。
这不是一个大问题,但我可以想象当你可以写时会有一些困惑
for k in i .. j { /* ... */ }
就像你可以为内置类型,但你必须使用
let w: &[type] = &v[range(i,j)];
用于切片。如果我想允许像i..
、..j
这样的切片被封装,那就更麻烦了。(..
切片在这里无关紧要,它无论如何都不会得到我的类型)。如果我这样做,我将需要三种类型的范围的构造函数,或者使用Option
进行一些丑陋的包装。
..
语法糖真的很巧妙,但根据我的探索,您不能对自己类型的范围使用那么多。你可以定义范围,通过一些黑客攻击,你可以迭代它们,但你不能用它们进行索引。
告诉我我错了,或者告诉我是否有什么技巧可以完成任务,甚至完成一半。或者,如果这确实是不可能的,请告诉我,这样我就可以停止在上面浪费时间,编写一个包装类并放弃..
运算符。
更新我在操场中放了一个简化的例子
不,你不能。
根据孤立规则的定义:
给定
impl<P1..=Pn> Trait<T1..=Tn> for T0
,只有当以下至少一项为真时,impl
才有效:
- 特质是一种地方特色
- 所有
- 至少有一个类型
T0..=Tn
必须是本地类型。设CCD_ 29是第一个这样的类型T0..Ti
中不可能出现未覆盖的类型参数P1..=Pn
(不包括Ti
)只有未覆盖类型参数的出现受到限制。请注意,为了连贯性的目的,基本类型是特殊的。方框中的T不被视为覆盖,方框被视为局部。
局部特征
在当前机箱中定义的
trait
。特征定义是局部的,或者不独立于应用的类型参数。给定trait Foo<T, U>
,无论T
和U
的类型如何,Foo
始终是局部的。
本地类型
在当前机箱中定义的
struct
、enum
或union
。这不受应用的类型参数的影响。struct Foo
被认为是局部的,但Vec<Foo>
不是。CCD_ 43是本地的。类型别名不影响位置。
由于Index
、Range
和Vec
都不是本地类型,并且Range
不是基本类型,因此无论在...
的位置放置什么,都不能使用impl<T> Index<Range<...>> for Vec<T>
。
这些规则的原因是没有什么能阻止Range
或Vec
实现impl<T, Idx> Index<Range<Idx>> for Vec<T>
。这样的impl不存在,也可能永远不会存在,但所有类型的规则都是相同的,在一般情况下,这种情况肯定会发生。
您也不能重载范围运算符-它总是创建一个Range
(或RangeInclusive
、RangeFull
等)
我能想到的唯一解决方案是为Vec
创建一个新类型的包装器,如注释中所建议的那样。
如果你想让你的向量返回一个包裹的切片,你可以使用一点不安全的代码:
use std::ops::{Index, IndexMut, Range, Deref, DerefMut};
#[repr(transparent)] // Because of this we can soundly cast `&{mut }[T]` to `&{mut }MySlice<T>`.
pub struct MySlice<T>([T]);
impl<'a, T> From<&'a [T]> for &'a MySlice<T> {
fn from(v: &'a [T]) -> &'a MySlice<T> {
unsafe { &*(v as *const [T] as *const MySlice<T>) }
}
}
impl<'a, T> From<&'a mut [T]> for &'a mut MySlice<T> {
fn from(v: &'a mut [T]) -> &'a mut MySlice<T> {
unsafe { &mut *(v as *mut [T] as *mut MySlice<T>) }
}
}
impl<T> Index<usize> for MySlice<T> {
type Output = T;
fn index(&self, idx: usize) -> &Self::Output { &self.0[idx] }
}
impl<T> IndexMut<usize> for MySlice<T> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output { &mut self.0[idx] }
}
impl<T> Index<Range<usize>> for MySlice<T> {
type Output = MySlice<T>;
fn index(&self, idx: Range<usize>) -> &Self::Output { self.0[idx].into() }
}
impl<T> IndexMut<Range<usize>> for MySlice<T> {
fn index_mut(&mut self, idx: Range<usize>) -> &mut Self::Output { (&mut self.0[idx]).into() }
}
// And so on, for all range types...
pub struct MyVec<T>(pub Vec<T>);
impl<T> Deref for MyVec<T> {
type Target = MySlice<T>;
fn deref(&self) -> &Self::Target { self.0.as_slice().into() }
}
impl<T> DerefMut for MyVec<T> {
fn deref_mut(&mut self) -> &mut Self::Target { self.0.as_mut_slice().into() }
}
我有一种解决方案,包括在我自己的机箱中安装一个新的Range
,以及大量的黑客攻击。完整的代码在操场上。这是一个缓慢的编译(我想有很多类型推断),所以你可能必须下载它才能运行它。对此感到抱歉。
有很多元编程可以定义新的索引类型,但其要点是我使用一个特性来指定不同的类型,并使用一个包装器来保存底层数字。
// A dummy type to illustrate the types I want to work with
// Different TypeInfo will give me different and incompatible
// types that wrap integers, which is what I want.
trait TypeInfo {
type WrappedType: num::PrimInt;
}
// A wrapper wraps the type from the TypeInfo trait;
// the TypeInfo trait specifies the type when I define what
// operations I will allow.
#[derive(Debug, Clone, Copy)]
struct Wrapper<T: TypeInfo>(T::WrappedType);
然后我有另一个特性来指定是否允许给定的索引在给定的序列类型中进行索引。
// Trait that determines which sequences an index can index
trait CanIndex<Seq: ?Sized> {}
尽管我不确定这是否是正确的解决方案。。。
无论如何,对于Rust的大多数类型,我需要使用usize
进行索引,所以我有另一个特性来指定我是否有这样的bug。
// For indexing... (You get too deeply nested genetics without it)
// Maybe I can get rid of it, but that is for another day...
pub trait IndexType {
fn as_index(&self) -> usize;
}
impl<TI> IndexType for Wrapper<TI>
where
TI: TypeInfo<WrappedType = usize>,
{
fn as_index(&self) -> usize {
self.0
}
}
虽然必须为每一种序列类型实现Index
,至少为每一个泛型类型,这并不让我感到高兴,但我不认为有任何方法可以绕过这一点。对于单一指数,它只是这样:
// Generic index implementation. (The real situation is a bit more
// complicated because I need to specify which types each index type
// is allowed to index, but this is the gist of it)
// Indexing a single element in a vector
impl<TI, T> std::ops::Index<Wrapper<TI>> for Vec<T>
where
TI: TypeInfo,
TI: CanIndex<Vec<T>>,
Wrapper<TI>: IndexType,
{
type Output = T;
fn index(&self, i: Wrapper<TI>) -> &Self::Output {
&self[i.as_index()]
}
}
// Indexing a single element in a slice
impl<TI, T> std::ops::Index<Wrapper<TI>> for [T]
where
TI: TypeInfo,
TI: CanIndex<[T]>,
Wrapper<TI>: IndexType,
{
type Output = T;
fn index(&self, i: Wrapper<TI>) -> &Self::Output {
&self[i.as_index()]
}
}
对于我的新型Range
,我选择了enum
。我想用它做的事情会更方便。
// My own type of ranges, now that I cannot use the built-in type...
/// Wrapper of Range that we can work with within Rust's type system
#[derive(Clone, Copy)]
pub enum Range<Idx> {
Closed(Idx, Idx), // start..end
Left(Idx), // start..
Right(Idx), // ..end
Full, // ..
}
类型需要一个构造函数,Rust在使用..
运算符时创建不同的类型会使构造函数变得复杂,但您可以这样做:
pub trait GenRange<R, Idx> {
fn range(r: R) -> Range<Idx>;
}
/*
Now implement that trait for the different range types.
Is there a better way?
*/
// Now I can build ranges with this function:
pub fn range<Idx, R: GenRange<R, Idx>>(r: R) -> Range<Idx> {
R::range(r)
}
有了这个,我还可以实现范围的索引。我可以使用CanIndex<>
来指定我可以使用任何类型的范围进行索引,我可以使用这些范围对单个项目进行索引。
impl<Idx, T> std::ops::Index<Range<Idx>> for Vec<T>
where
Idx: IndexType,
Idx: CanIndex<Vec<T>>,
{
type Output = [T];
fn index(&self, r: Range<Idx>) -> &Self::Output {
match r {
Range::Closed(start, end) => &self[start.as_index()..end.as_index()],
Range::Left(start) => &self[start.as_index()..],
Range::Right(end) => &self[..end.as_index()],
Range::Full => &self[..],
}
}
}
impl<Idx, T> std::ops::Index<Range<Idx>> for [T]
where
Idx: IndexType,
Idx: CanIndex<[T]>,
{
type Output = [T];
fn index(&self, r: Range<Idx>) -> &Self::Output {
match r {
Range::Closed(start, end) => &self[start.as_index()..end.as_index()],
Range::Left(start) => &self[start.as_index()..],
Range::Right(end) => &self[..end.as_index()],
Range::Full => &self[..],
}
}
}
它变得相当复杂,所以我需要尽可能多地清理它,但至少我开始相信它是可以做到的。完成后,我需要找到一个好方法,将常用的std::ops::Range
接口添加到Range
中。但这将适用于今天。