有关字符串文字类型的示例,请参阅此 TypeScript 文档:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types
对于示例用例,我希望能够创建一个用于对表格数据执行操作的库,其中表的列被命名并具有异构类型。
假设我有一张这样的表:
| 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 |
Run Code Online (Sandbox Code Playgroud)
我希望能够获得这样的编译时间保证:
| 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 |
Run Code Online (Sandbox Code Playgroud)
我知道我可以使用类似的东西来表示异构类型的通用表,Table<(String, usize, usize, ...)>但不清楚如何嵌入列名称,同时还允许类型能够在以下操作的情况下动态更改:
let result1: Vec<String> = table.get_column("name"); // ["Bob", "Alice", "Eve"]
let result2: Vec<usize> = table.get_column("age"); // [12, 17, 13]
Run Code Online (Sandbox Code Playgroud)
Rust 有没有办法做到这一点?
我的一个想法是使用宏。宏是否能够产生类型错误,因此也许......
table.add_column("quiz5", column_values); // returns a new Table that is typed with one more column
Run Code Online (Sandbox Code Playgroud)
...根据传递的字符串文字输入为Vec<String>or (或作为错误)?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>());
}
Run Code Online (Sandbox Code Playgroud)
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>());
}
Run Code Online (Sandbox Code Playgroud)
此实现的一个缺陷是它不能保证在编译时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
}
Run Code Online (Sandbox Code Playgroud)
[src/main.rs:50] table.get_column::<Name>() = [
"Bob",
"Alice",
]
[src/main.rs:51] table.get_column::<Age>() = [
12,
17,
]
Run Code Online (Sandbox Code Playgroud)
这可能在某些方面变得更符合人体工程学,但我希望它能展示如何做到这一点。Rust 显然没有字符串文字类型(我认为没有任何东西可以与 Typescript 所具有的类型灵活性相匹配),但使用更传统的结构类型来实现您的目标并不太困难。
| 归档时间: |
|
| 查看次数: |
2099 次 |
| 最近记录: |