已知在 rust 中有一个表示范围的语法,一般用于提取数组的一部分,例如:
let arr = [0, 1, 2, 3, 4];
assert_eq!(arr[ .. ], [0, 1, 2, 3, 4]); // This is the `RangeFull`
assert_eq!(arr[ .. 3], [0, 1, 2 ]); // This is a `RangeTo`
assert_eq!(arr[ ..=3], [0, 1, 2, 3 ]);
assert_eq!(arr[1.. ], [ 1, 2, 3, 4]); // This is a `RangeFrom`
assert_eq!(arr[1.. 3], [ 1, 2 ]); // This is a `Range`
assert_eq!(arr[1..=3], [ 1, 2, 3 ]);
因为 rust 是一门自举的语言,所有的语言特性,其实我们也可以使用,正如上面注释中说的一样,ta其实是实现了四种结构体。
考虑我们要建的函数,需要用如下的方式调用
my_fun(..);
my_fun(1..);
my_fun(..10);
my_fun(10..20);
如何组合这四种情况呢,最先想到的就是使用枚举,把这四种情况包含在一起,作为一个类型使用:
use std::ops::{Range, RangeFull, RangeFrom, RangeTo};
pub enum ContentRange {
Range(Range<i32>),
RangeFull(RangeFull),
RangeFrom(RangeFrom<i32>),
RangeTo(RangeTo<i32>),
}
使用这个枚举进行函数声明:
fn my_fun(range: ContentRange) -> {
todo!();
}
带入上面的参数进行调用,发现是不可行的,提示类型不匹配,那么我们应该怎么办呢?于是此时我想到的
Into,我们可以这么改
use std::ops::{Range, RangeFull, RangeFrom, RangeTo};
pub enum ContentRange {
Range(Range<i32>),
RangeFull(RangeFull),
RangeFrom(RangeFrom<i32>),
RangeTo(RangeTo<i32>),
}
impl From<Range<i32>> for ContentRange {
fn from(r: Range<i32>) -> Self {
Self::Range(r)
}
}
impl From<RangeFull> for ContentRange{
fn from(f: RangeFull) -> Self {
Self::RangeFull(f)
}
}
impl From<RangeFrom<i32>> for ContentRange{
fn from(f: RangeFrom<i32>) -> Self {
Self::RangeFrom(f)
}
}
impl From<RangeTo<i32>> for ContentRange {
fn from(t: RangeTo<i32>) -> Self {
Self::RangeTo(t)
}
}
fn my_fn<R: Into<ContentRange>>(range: R){
todo!();
}
按照这样改,就可以实现我们之前想要的效果了,此时运行:
my_fun(..);
my_fun(1..);
my_fun(..10);
my_fun(10..20);
不再报类型错误。
进一步思考,我们可以发现,所谓的范围,只需要知道上边界和下边界的值就可以了,我们完全可以搞一搞结构体来替代上面的枚举:
use std::ops::{Range, RangeFull, RangeFrom, RangeTo};
pub struct ContentRange {
start: Option<i32>,
end: Option<i32>,
}
impl From<Range<i32>> for ContentRange {
fn from(r: Range<i32>) -> Self {
Self {
start: Some(r.start),
end: Some(r.end)
}
}
}
impl From<RangeFull> for ContentRange{
fn from(f: RangeFull) -> Self {
Self{
start: None,
end: None,
}
}
}
impl From<RangeFrom<i32>> for ContentRange{
fn from(f: RangeFrom<i32>) -> Self {
Self{
start: Some(f.start),
end: None,
}
}
}
impl From<RangeTo<i32>> for ContentRange {
fn from(t: RangeTo<i32>) -> Self {
Self{
start: None,
end: Some(t.end),
}
}
}
关于使用场景
最近在做 OSS 的下载文件功能,涉及到一个分片下载,参考 文档 我们可以发现,分片下载是支持多种形式的,当然我们可以用两个 Option<u32> 来实现,但是用 .., 10..20 等这种方式,有更好的表现力,减少使用者的心智负担。