如何将带有生命周期的通用函数转换为函数指针

3

当然,这只是我实际问题的简化版本。

fn my_func<'a, 'b>(a: &'a i32, b: &'b i32) -> i32 {
    a + b
}

fn my_func_generic<'a, 'b, T>(a: &'a T, b: &'b T) -> T
where
    &'a T: std::ops::Add<&'b T, Output = T>,
{
    a + b
}

fn apply_function(x: &i32, y: &i32, function: fn(&i32, &i32) -> i32) -> i32 {
    function(x, y)
}

fn main() {
    let x = 10;
    let y = 11;
    println!("{}", my_func(&x, &y));
    println!("{}", my_func_generic(&x, &y));

    println!("{}", apply_function(&x, &y, my_func));
    println!("{}", apply_function(&x, &y, my_func_generic));
}

error[E0308]: mismatched types
  --> src/main.rs:23:43
   |
23 |     println!("{}", apply_function(&x, &y, my_func_generic));
   |                                           ^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'r, 's> fn(&'r i32, &'s i32) -> _`
              found fn pointer `fn(&i32, &i32) -> _`

我有一个问题:如何将my_func_generic用作函数指针传递给apply_function

它绝对可以作为这样的函数使用,正如println!("{}", my_func_generic(&x, &y))所示。

尝试将其写成指针会产生相同的错误:
let function_pointer: fn(&i32, &i32) -> i32 = my_func_generic;

手动注释泛型类型会出现相同的错误:
let function_pointer: fn(&i32, &i32) -> i32 = my_func_generic::<'_, '_, i32>;

很遗憾,我不理解这个错误信息,并且对于Rust泛型内部类型系统的知识不够,无法理解如何解决这个问题,或者是否由于某些机制的未知原因而根本不可能解决。

2个回答

4

简而言之:在您的情况下,您可以按以下方式操作:

fn apply_function<'a, 'b>(x: &'a i32, y: &'b i32, function: fn(&'a i32, &'b i32) -> i32) -> i32

但总的来说,这是不可能的。


在Rust中有两种生命周期:晚期绑定早期绑定。这两者之间的区别在于我们将泛型生命周期绑定到具体生命周期的时间。
对于早期绑定生命周期,当您引用它们的函数时,我们会将它们绑定。对于晚期绑定生命周期,引用它们的函数会给您一个函数指针(或项目),该函数指针是基于生命周期的通用类型,我们称之为HRTB(for<'lifetime> fn(...)),并且我们仅在调用时选择实际生命周期。
重要的是,任何生命周期的限制都会将其转换为早期绑定生命周期。这是因为通常不可能将受约束的生命周期表示为HRTB(有关更多详细信息,请参见上面链接的rustc-dev-guide页面),尽管老实说我仍然不完全理解原因。
所以在my_func_generic()中,'a'b是早期绑定的。然而,apply_function()希望它们是后期绑定的(因为函数指针中省略生命周期的默认展开是HRTB,fn(&i32, &i32) -> i32实际上是for<'a, 'b> fn(&'a i32, &'b i32) -> i32),因此这是一种类型不匹配。

在这种情况下,您可以将apply_function()更改为将生命周期绑定为参数,如我上面建议的那样。这仍然接受后期绑定的生命周期,因为我们将立即绑定它们。但是,据我所知,通常无法将早期绑定的生命周期转换为后期绑定的生命周期


“重要的是,任何对生命周期的限制都会将其转化为早期绑定的生命周期。” - 这就是为什么@hkBst的答案有效吗? - Finomnis
我猜你可以更一般地解决这个问题,写成:fn apply_function<'a, 'b, 'a2, 'b2>(x: &'a i32, y: &'b i32, function: fn(&'a2 i32, &'b2 i32) -> i32) -> i32 where 'a: 'a2, 'b: 'b2playground - rodrigo
在几乎所有情况下,'a : 'a2 是不是多余的?因为你可以直接使用 'a - Finomnis
@Finomnis:在大多数情况下,你甚至可以在任何地方只使用一个生命周期,编译器会自动猜测正确的生命周期。但是根据 OP 的代码,我在想如果 apply_function() 调用了 function(x, y) + function(y, x),那么 ChayimFiredman 的解决方案将不起作用,但我的解决方案 where 'a: 'a2 + 'b2, 'b: 'a2 + 'b2 将会奏效。 - rodrigo
@Finomnis 是的,这就是另一个答案的工作原理。您不会限制生命周期,因此它们是后期绑定的。 - Chayim Friedman
显示剩余2条评论

1

如果您稍微更改一下特质边界,它就能正常工作:

fn my_func_generic<'a, 'b, T>(a: &'a T, b: &'b T) -> T
where
    T: Copy + std::ops::Add<Output = T>,
{
    *a + *b
}

为什么这样呢?特质约束与此有什么关系? - Finomnis
@Finomnis,我不知道为什么这个有效。 - hkBst
1
除了@ChayimFriedman的答案之外,我现在认为我理解了原因。 “重要的是,任何对生命周期的限制都会将其转换为早期绑定生命周期。” - Finomnis
@Finomnis,这可能确实是解释。 - hkBst

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接