#type #box #dynamic #static #no-std

no-std bin+lib unibox

使用静态或动态内存存储任何类型的通用盒

2个不稳定版本

0.2.0 2021年11月26日
0.1.0 2021年11月20日

#2574 in Rust模式

MIT许可证

33KB
638

UniBox

通用盒。

通常,当我们想在集合中存储不同类型时,我们会使用以下技术之一

  1. 一个枚举来封装所有可能的类型。
  2. 所有类型都必须实现的一个boxed trait。

有时上述技术都不适用。类型的集合可能事先未知,例如在库中。当我们实现一个trait时,我们只能访问trait中定义的方法,而不能访问原始结构的所有方法和属性。此外,我们还需要使用Box,这意味着分配动态内存,使得no_std应用程序更加复杂。

如果您遇到了这些限制中的任何一种,UniBox可能正是您用例的可行解决方案。

UniBox可以

  • 在不使用结构签名中的泛型的情况下存储泛型类型。
  • 使用静态或动态内存,您来决定。
  • 返回任何类型的引用。
  • 用于在集合或数组中存储混合数据。

UniBox提供两种类型的类型

  • 静态:不使用堆存储数据的unibox。它们有一个固定的大小,它们所承载的类型不能比这更大。目前有四种类型:UniBox32UniBox64UniBox128UniBox256,分别用于存储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_refUniboxed::as_mut_ref是不安全操作的主要原因。

为什么不使用Any呢?

Any特质提供了类似的功能,它允许将泛型类型进行转换,但与uniboxes相比有一些限制。

  1. 只能使用具有静态引用的类型,因此类似以下内容无法在Box<dyn Any>中分配。
struct MyStruct<'a> {
    my_ref: &'a [i32]
}
  1. dyn Any的大小在编译时是无法知道的,因此我们无法用它来在数组或其他非堆内存结构中存储泛型类型。

功能和no_std

这个crate是no_std,但它使用alloc crate在UniBox内部分配动态内存。这通过一个名为alloc的默认功能来控制,该功能默认启用。

如果你的环境不提供alloc crate,只需禁用默认功能。这样做之后,你将无法使用UniBox类型。

无运行时依赖

功能