通用 HKT 与生命周期有界限

问题描述 投票:0回答:1

在为动态类型 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>
,它就可以工作,但我不知道如何表达泛型类型。

rust lifetime
1个回答
0
投票

请注意,

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)的情况下,您可以考虑两种解决方案:

创建自己的函子特征和类型(playground

创建您自己的仿函数特征

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。

© www.soinside.com 2019 - 2024. All rights reserved.