在为动态类型 DSL 创建解释器时,由于需要对所有参数进行类型检查,编写内置函数很快就会变得乏味。为了解决这个问题,我创建了一个可以为我做到这一点的特质。
DowncastArg
从枚举&Value
转换为其变体。
ExtractArgs
特性将参数列表 &[Value]
转换为参数元组,每个参数都向下转换为其正确的类型。
我已经几乎工作了,但我遇到了终身问题(游乐场):
#[derive(Debug, Eq, PartialEq)]
enum Value {
Int(i64),
String(String),
}
trait DowncastArg<'a>: Sized + 'a {
fn downcast_arg(value: &'a Value) -> Option<Self>;
}
impl DowncastArg<'_> for i64 {
fn downcast_arg(value: &Value) -> Option<Self> {
if let Value::Int(variant) = value {
Some(*variant)
} else {
None
}
}
}
impl<'a> DowncastArg<'a> for &'a str {
fn downcast_arg(value: &'a Value) -> Option<Self> {
if let Value::String(variant) = value {
Some(variant)
} else {
None
}
}
}
trait ExtractArgs<'a>: Sized + 'a {
fn extract_args(args: &'a [Value]) -> Result<Self, String>;
}
impl<'a, V: DowncastArg<'a>> ExtractArgs<'a> for V {
fn extract_args(args: &'a [Value]) -> Result<Self, String> {
match args {
[value] => V::downcast_arg(value)
.ok_or_else(|| format!("invalid argument {value:?}")),
_ => Err("invalid argument count".to_owned()),
}
}
}
// Also impl for tuples:
// impl<'a, V1, V2, ...> ExtractArgs<'a> for (V1, V2, ...)
// where
// V1: DowncastArg<'a>,
// V2: DowncastArg<'a>,
// ...
// { ... }
struct Function(Box<dyn Fn(&[Value]) -> Result<Value, String>>);
impl Function {
fn call(&self, args: &[Value]) -> Result<Value, String> {
self.0(args)
}
}
fn create_function_raw<Func, Args>(f: Func) -> Box<dyn Fn(&[Value]) -> Result<Value, String>>
where
Func: Fn(Args) -> Result<Value, String>,
Func: 'static,
for<'a> Args: 'a + ExtractArgs<'a>,
{
let f = move |args: &[Value]| -> Result<Value, String> {
let extracted_args = Args::extract_args(args)?;
f(extracted_args)
};
Box::new(f)
}
fn create_function<Func, Args>(f: Func) -> Function
where
Func: Fn(Args) -> Result<Value, String>,
Func: 'static,
for<'a> Args: 'a + ExtractArgs<'a>,
{
Function(create_function_raw(f))
}
fn main() {
let successor = create_function(|i: i64| Ok(Value::Int(i + 1)));
assert_eq!(
successor.call(&[Value::Int(1)]),
Ok(Value::Int(2)),
);
let len = create_function(|s: &str| Ok(Value::Int(s.len() as i64)));
assert_eq!(
len.call(&[Value::String("abc".to_owned())]),
Ok(Value::Int(3)),
);
}
这是编译错误:
error: implementation of `ExtractArgs` is not general enough
--> src/main.rs:90:15
|
90 | let len = create_function(|s: &str| Ok(Value::Int(s.len() as i64)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `ExtractArgs` is not general enough
|
= note: `&str` must implement `ExtractArgs<'0>`, for any lifetime `'0`...
= note: ...but it actually implements `ExtractArgs<'1>`, for some specific lifetime `'1`
对于具体类型,如果我将这些行
for<'a> Args: 'a + ExtractArgs<'a>
替换为 for<'a> &'a str: ExtractArgs<'a>
,它就可以工作,但我不知道如何表达泛型类型。
请注意,
Func: Fn(Args) -> Result<Value, String>
的 create_function
类型参数不受更高等级特征界限 (HRTB) 的限制。此外,类型参数 Args
是 single 类型(例如 &'0 str
),而不是更高种类的类型 (for<'1> &'1 str
)。因此,编译器不会意识到您需要闭包在 '1
参数的任意生命周期 &str
内保持通用,而是为 &str
参数分配特定的生命周期 '0
。
如果 Rust 有更高种类的类型,解决方案是使
Args
在整个生命周期内通用,并将 Func
与 HRTB 绑定:Func: for<'a> Fn(Args<'a>) -> Result<Value, String>
。这将导致编译器正确地将闭包参数的类型推断为更高种类的类型for<'1> &'1 str
。
在缺乏更高种类的类型(即今天的 Rust)的情况下,您可以考虑两种解决方案:
创建您自己的仿函数特征
Funct
并在 create_function
和 create_function_raw
中使用它:
trait Funct {
type Args<'a>: ExtractArgs<'a> + 'a;
fn call<'a>(&self, args: Self::Args<'a>) -> Result<Value, String>;
}
fn create_function_raw<Func: Funct + 'static>(
f: Func,
) -> Box<dyn Fn(&[Value]) -> Result<Value, String>> {
let f = move |args: &[Value]| -> Result<Value, String> {
let extracted_args = Func::Args::extract_args(args)?;
f.call(extracted_args)
};
Box::new(f)
}
fn create_function<Func: Funct + 'static>(f: Func) -> Function {
Function(create_function_raw(f))
}
并创建并使用您自己的函子(
Successor
和Len
)而不是闭包:
struct Successor;
impl Funct for Successor {
type Args<'a> = i64;
fn call<'a>(&self, args: Self::Args<'a>) -> Result<Value, String> {
Ok(Value::Int(args + 1))
}
}
let successor = create_function(Successor);
struct Len;
impl Funct for Len {
type Args<'a> = &'a str;
fn call<'a>(&self, args: Self::Args<'a>) -> Result<Value, String> {
Ok(Value::Int(args.len() as i64))
}
}
let len = create_function(Len);
不幸的是,由于同样的问题,为通用闭包
Funct
实现 Func: Fn(Args) -> Result<Value, String>
将不起作用,并且对于不同的 Args
值手动实现它在没有专门化的情况下将无法工作。这种方法变得笨拙,虽然可以使用宏来减少样板,但它并不像直接使用闭包那么方便。
DowncastArg
和ExtractArgs
特征第二种方法(也是我自己更喜欢的方法)是重新设计
DowncastArg
和 ExtractArgs
特征,以便 ExtractArgs
提供通用关联类型 (GAT) ExtractArgs::Extracted<'a>
,我们可以将其用作闭包参数类型:
trait DowncastArg {
fn downcast_arg(value: &Value) -> Option<&Self>;
}
// impls
trait ExtractArgs {
type Extracted<'a>;
fn extract_args<'a>(args: &'a [Value]) -> Result<Self::Extracted<'a>, String>;
}
impl<V: DowncastArg + ?Sized + 'static> ExtractArgs for V {
type Extracted<'a> = &'a Self;
fn extract_args<'a>(args: &'a [Value]) -> Result<Self::Extracted<'a>, String> {
// Same impl
}
}
这允许我们使用 HRTB 作为
create_function
的闭包参数:
fn create_function<Func, Args>(f: Func) -> Function
where
Func: for<'a> Fn(Args::Extracted<'a>) -> Result<Value, String>,
Func: 'static,
Args: ExtractArgs + ?Sized,
{
Function(create_function_raw(f))
}
这确实有一个缺点,即编译器无法在
Args
调用中推断 create_function
的类型,并且必须手动指定其值:
let successor = create_function::<_, i64>(|i| Ok(Value::Int(*i + 1)));
如果您愿意,可以通过将
create_function
移动到 ExtractArgs
特征来解决此问题,这样它就可以作为 ExtractArgs
类型 (str::create_function(|s| Ok(Value::Int(s.len() as i64)))
) 的关联函数进行调用:
trait ExtractArgs {
// ...
fn create_function(f: impl for<'a> Fn(Self::Extracted<'a>) -> Result<Value, String> + 'static) -> Function {
Function(create_function_raw(f))
}
}
注意:为了简单起见,我将
DowncastArg::downcast_arg
返回 &Self
。如果您希望实现能够返回其他类型(例如 DowncastArg
的 i64
impl 如何返回 i64
),您可以将 Downcasted<'a>
GAT 添加到 DowncastArg
并在 downcast_arg
中返回它,类似于 Extracted<'a>
中的 ExtractArgs
GAT。