有没有一种方法可以在Rust中模拟字符串文字类型



有关字符串文字类型的示例,请参阅以下TypeScript文档:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-类型

对于一个示例用例,我希望能够创建一个库来对表格数据执行操作,其中表的列是命名的,并且具有异构类型。

假设我有一张这样的桌子:

| name    | age | quiz1 | quiz2 | midterm | quiz3 | quiz4 | final |
| ------- | --- | ----- | ----- | ------- | ----- | ----- | ----- |
| "Bob"   | 12  | 8     | 9     | 77      | 7     | 9     | 87    |
| "Alice" | 17  | 6     | 8     | 88      | 8     | 7     | 85    |
| "Eve"   | 13  | 7     | 9     | 84      | 8     | 8     | 77    |

我希望能够获得这样的编译时间保证:

let result1: Vec<String> = table.get_column("name"); // ["Bob", "Alice", "Eve"]
let result2: Vec<usize> = table.get_column("age"); // [12, 17, 13]

我知道我可以使用Table<(String, usize, usize, ...)>之类的东西来表示异构类型的通用表,但不清楚我将如何嵌入列名,同时允许类型在以下操作的情况下动态更改:

table.add_column("quiz5", column_values); // returns a new Table that is typed with one more column

在Rust中有办法做到这一点吗?

我的一个想法是使用宏。宏是否能够产生类型错误,因此。。。

let result = get_column!(table, "column_name");

是否根据传递的字符串文字键入为Vec<String>Vec<usize>(或作为错误(?

您可以实现您想要的保证,但不能通过字符串文字。

为了在编译时检查列类型是否与您要求的列匹配,您需要创建具有关联类型(数据类型(的标记类型(列的名称(。像这样的东西是可行的:

use std::any::Any;
use std::collections::HashMap;
trait Column {
type Data: 'static;
const NAME: &'static str;
}
struct Name;
impl Column for Name {
type Data = String;
const NAME: &'static str = "name";
}
struct Age;
impl Column for Age {
type Data = usize;
const NAME: &'static str = "age";
}
struct Table {
data: HashMap<&'static str, Box<dyn Any>>,
}
impl Table {
fn new() -> Table {
Table {
data: HashMap::new()
}
}
fn set_column<C: Column>(&mut self, data: Vec<C::Data>) {
self.data.insert(C::NAME, Box::new(data));
}
fn get_column<C: Column>(&self) -> &Vec<C::Data> {
self.data
.get(C::NAME)
.and_then(|data| data.downcast_ref::<Vec<C::Data>>())
.expect("table does not have that column")
}
}
fn main() {
let mut table = Table::new();

table.set_column::<Name>(vec!["Bob".to_owned(), "Alice".to_owned()]);
table.set_column::<Age>(vec![12, 17]);

dbg!(table.get_column::<Name>());
dbg!(table.get_column::<Age>());
}
[src/main.rs:50] table.get_column::<Name>() = [
"Bob",
"Alice",
]
[src/main.rs:51] table.get_column::<Age>() = [
12,
17,
]

此实现的一个缺陷是,它不能保证在编译时Table实际上包含您要查找的列。为此,您需要将列类型编码为您建议的表类型:Table<(Name, Age, ...)>。它还需要允许编译时查找((Name, Age, ...)包含Age吗?(和扩展类型的能力((Name,)+Age=>(Name, Age)(。这是一个令人生畏的模板杂耍,你必须处理,但有一些板条箱可以提供这种功能。

这里有一个使用lhlist的工作示例(不一定提倡它,它只是我发现的一个机箱,可以很好地用于演示目的(。它有一个类似于我们上面的API,不仅具有我们需要的表达能力,还允许我们将数据与各个列类型关联起来:

#[macro_use]
extern crate lhlist;
use lhlist::{Label, LVCons, LookupElemByLabel, LabeledValue, Value, Nil};
new_label!(Name: Vec<String>);
new_label!(Age: Vec<usize>);
new_label!(Grade: Vec<usize>);
struct Table<Columns> {
columns: Columns
}
impl Table<Nil> {
fn new() -> Table<Nil> {
Table { columns: Nil::default() }
}
}
impl<Columns> Table<Columns> {
fn add_column<C>(self, data: C::AssocType) -> Table<LVCons<C, Columns>> 
where 
C: Label + 'static
{
Table { columns: lhlist::cons(lhlist::labeled_typearg::<C>(data), self.columns) }
}
fn get_column<C>(&self) -> &C::AssocType
where
C: Label + 'static,
Columns: LookupElemByLabel<C, Elem = LabeledValue<C>>,
{
self.columns.elem().value_ref()
}
}
fn main() {
let table = Table::new();
let table = table.add_column::<Name>(vec!["Bob".to_owned(), "Alice".to_owned()]);
let table = table.add_column::<Age>(vec![12, 17]);
dbg!(table.get_column::<Name>());
dbg!(table.get_column::<Age>());
// dbg!(table.get_column::<Grade>()); // compile-time error
}
[srcmain.rs:42] table.get_column::<Name>() = [
"Bob",
"Alice",
]
[srcmain.rs:43] table.get_column::<Age>() = [
12,
17,
]

这可能会在几个方面变得更符合人体工程学,但我希望它能展示如何做到这一点。Rust显然没有字符串文字类型(我认为没有任何东西能与Typescript的类型灵活性相匹配(,但使用更传统的结构类型来实现目标并不太难。

您可以使用下转换来获得相同的行为(如果没有它,您的需求似乎不可能实现,编译器无法知道运行时会发生什么(:

pub struct Table {
columns: HashMap<String, Box<dyn AnyColumn>>,
}
impl Table {
pub fn new() -> Self {
Self {
columns: HashMap::new(),
}
}
pub fn add_column(&mut self, column: impl AnyColumn) -> Option<Box<dyn AnyColumn>> {
self.columns.insert(column.name().to_string(), Box::new(column))
}
pub fn get_column<E, T: Column<E>>(&self, name: &str) -> Option<&T> {
self.columns.get(name).and_then(|c| (c.deref() as &dyn Any).downcast_ref::<T>())
}
pub fn get_column_mut<E, T: Column<E>>(&mut self, name: &str) -> Option<&mut T> {
self.columns.get_mut(name).and_then(|c| (c.deref_mut() as &mut dyn Any).downcast_mut::<T>())
}
}
pub struct VecColumn<T> {
name: String,
data: Vec<T>,
}
impl<T: 'static> AnyColumn for VecColumn<T> {
fn name(&self) -> &str {
&self.name
}
}
impl<T: 'static> Column<T> for VecColumn<T> {}
impl<T> Index<usize> for VecColumn<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
&self.data[index]
}
}
impl<T> IndexMut<usize> for VecColumn<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.data[index]
}
}
pub trait AnyColumn: Any {
fn name(&self) -> &str;
}
pub trait Column<T>: IndexMut<usize, Output = T> + AnyColumn {}

最新更新