2个不稳定版本
0.2.0 | 2021年11月26日 |
---|---|
0.1.0 | 2021年11月20日 |
#2574 in Rust模式
33KB
638 行
UniBox
通用盒。
通常,当我们想在集合中存储不同类型时,我们会使用以下技术之一
- 一个枚举来封装所有可能的类型。
- 所有类型都必须实现的一个boxed trait。
有时上述技术都不适用。类型的集合可能事先未知,例如在库中。当我们实现一个trait时,我们只能访问trait中定义的方法,而不能访问原始结构的所有方法和属性。此外,我们还需要使用Box,这意味着分配动态内存,使得no_std
应用程序更加复杂。
如果您遇到了这些限制中的任何一种,UniBox可能正是您用例的可行解决方案。
UniBox可以
- 在不使用结构签名中的泛型的情况下存储泛型类型。
- 使用静态或动态内存,您来决定。
- 返回任何类型的引用。
- 用于在集合或数组中存储混合数据。
UniBox提供两种类型的类型
- 静态:不使用堆存储数据的unibox。它们有一个固定的大小,它们所承载的类型不能比这更大。目前有四种类型:
UniBox32
、UniBox64
、UniBox128
和UniBox256
,分别用于存储32、64、128和256字节的类型。所有这些类型都是基于泛型静态类型UniBoxN
,也可以用于实现自定义静态unibox。 - 动态:通过分配内存来存储数据,就像普通的Box一样。只有一个类型,
UniBox
。
用法
假设我们有两个不同的结构体,它们之间几乎没有共同点,如下面的User和Server。我们想在同一个集合中存储这些类型的实例。
我们可以这样使用静态unibox
use unibox::{ Uniboxed, UniBox64 };
#[derive(Debug)]
struct BornDate {
pub year: u16,
pub month: u8,
pub day: u8,
}
#[derive(Debug)]
struct User {
pub name: String,
pub lastname: String,
pub born: BornDate,
}
#[derive(Debug)]
struct Server {
pub domain: String,
pub port: u16
}
let ubox_usr = UniBox64::new(
User {
name: "John".to_owned(),
lastname: "Dow".to_owned(),
born: BornDate {
year: 1984,
month: 12,
day: 25
}
}
).expect("Couldn't create UniBox64 for User");
let ubox_server = UniBox64::new(
Server {
domain: "example.com".to_owned(),
port: 8080
}
).expect("Couldn't create UniBox64 for Server");
// Create a vector with the uniboxes
let v = vec!(ubox_usr, ubox_server);
for ubox in v.iter() {
match ubox.id() {
"my_crate::User" => {
// It's a User struct
println!("{:#?}", unsafe { ubox.as_ref::<User>() });
},
"my_crate::Server" => {
// It's a Server struct
println!("{:#?}", unsafe { ubox.as_ref::<Server>() });
},
_ => {}
}
}
动态版本UniBox
的工作方式完全相同,唯一的区别是它会分配内存来存储类型,因此您不必担心大小。
使用引用进行uniboxing类型
可以unibox一个包含非静态生命周期引用的类型,如下所示
struct MyStruct<'a> {
my_ref: &'a [i32]
}
let arr = [1, 2, 3, 4, 5];
let ubox = UniBox32::new(
MyStruct {
my_ref: &arr
}
).expect("Failed uniboxing MyStruct");
println!("{:#?}", unsafe { ubox.as_ref::<MyStruct>() }.my_ref);
但是一旦类型被嵌入到UniBox中,Rust编译器就会失去对其的跟踪,将无法确保生命周期约束得到遵守。因此,程序员必须确保在原始值被丢弃后不再使用任何引用。这也是为什么Uniboxed::as_ref
和Uniboxed::as_mut_ref
是不安全操作的主要原因。
为什么不使用Any
呢?
Any
特质提供了类似的功能,它允许将泛型类型进行转换,但与uniboxes相比有一些限制。
- 只能使用具有静态引用的类型,因此类似以下内容无法在
Box<dyn Any>
中分配。
struct MyStruct<'a> {
my_ref: &'a [i32]
}
dyn Any
的大小在编译时是无法知道的,因此我们无法用它来在数组或其他非堆内存结构中存储泛型类型。
功能和no_std
这个crate是no_std
,但它使用alloc
crate在UniBox
内部分配动态内存。这通过一个名为alloc
的默认功能来控制,该功能默认启用。
如果你的环境不提供alloc crate,只需禁用默认功能。这样做之后,你将无法使用UniBox
类型。