在你自己的 rust 函数中支持 .. 或 10..20 参数

144 阅读2分钟

已知在 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 等这种方式,有更好的表现力,减少使用者的心智负担。

相关 github 代码