在Rust语言中,是否可以从函数返回一个静态函数?

3
假设我在Rust中有以下类型:
type UnOp = fn(u64) -> u64;

那种类型允许我创建不同的一元操作:
const add1 : UnOp = |x| x + 1;
const mul2 : UnOp = |x| x * 2;

假设我需要在代码不同位置,对不同的数字进行 add2add3add4(...)等操作。写下所有定义会很繁琐,因此我写了一个通用的 adder 函数:

fn adder(add : u64) -> UnOp {
  |x| x + add
}

这将允许我编写 add(2), add(3)等任何数字的代码:
// Prints 2 + 40
println!("result is: {}", adder(2)(40))

问题是: adder 实际上不是有效的 Rust 代码,因为 |x| ... 是一个闭包,而不是静态函数。要使 adder 按我希望的方式工作,需要将 UnOp 修改为闭包形式:
type UnOp = Box<dyn Fn(u64) -> u64>;

fn adder(add : u64) -> UnOp {
  Box::new(move |x| x + add)
}

问题是:如果我知道每个adder(N)的使用都应用于静态值,怎么办?
在这种情况下,创建动态闭包会在计算方面浪费资源。不仅如此,Box<dyn ...>会极大地复杂化代码,并且甚至可能需要生命周期注释。我的问题是:
是否有可能创建adder而不修改UnOp的原始定义?也就是让UnOp成为一个静态函数,而不是闭包?
从逻辑上讲,没有理由不可能,只要adder的参数是静态的,Rust 应该能够在编译时将其展开,生成每个特定实例。
编辑:附加细节
@Netwave 的回答提出了一个很好的解决方案,针对我给出的具体示例解决了这个问题。但遗憾的是,如果UnOp是多态的,或者常量参数本身是一个函数,则无法解决这个问题。
type UnOp<A> = fn(A) -> A;

pub fn adder<const ADD: u64>() -> UnOp<u64> {
  |x| ADD + x
}

pub fn apply<A, const op : UnOp<A>, const x : A>() -> A {
  return op(x);
}

这引发了2个错误:

- the type of const parameters must not depend on other generic parameters

- using function pointers as const generic parameters is forbidden
2个回答

5
你可以使用 常量泛型 来实现:
type UnOp = fn(u64) -> u64;


const fn adder<const ADD: u64>(x: u64) -> u64 {
    ADD + x
}

fn main() {
    let add_1: UnOp = adder::<1>;
    println!("{}", add_1(1));
}

游乐场


这太棒了,正是我在寻找的。但我注意到如果UnOp是多态的,或者const参数是一个函数,它就不起作用。我已经编辑了问题,展示了这个解决方案不起作用的例子。我想知道是否有一种方法可以规避这个限制?无论如何,你的答案是非常准确的,尽管它没有涵盖我的实际情况。 - MaiaVictor
@MaiaVictor 我认为你应该开一个新的相关问题,而不是编辑原来的问题。 - Netwave
好的,我稍后会这样做。谢谢。 - MaiaVictor

2

是否可以创建加法器,而不修改UnOp的原始定义?也就是说,让UnOp成为一个静态函数,而不是闭包?

由于您没有解释原因,我将忽略此要求。我认为您无谓地限制了自己的选择。

您可以使用泛型和trait的巧妙方法:

trait UnOp {
    fn call(&self, _: u64) -> u64;
}

impl<F> UnOp for F
where
    F: Fn(u64) -> u64,
{
    fn call(&self, x: u64) -> u64 {
        self(x)
    }
}

fn adder(add: u64) -> impl UnOp {
    move |x| x + add
}

我解释一下为什么。动态闭包比静态函数更昂贵吗?从代码上来说,由于添加了臃肿的东西,也更难维护。 - MaiaVictor
@MaiaVictor 不是很清楚,闭包只是能够捕获环境的函数,你可以手动完成而不会有问题,只是写起来会更长。请注意,在我的示例中,闭包对用户是不可见的,你可以返回Fn(u64) -> u64,但这样就会暴露UnOp的实现细节,取决于你自己的选择。 - Stargateur
我需要考虑一下。我的代码确实使用了闭包,但这导致它需要生命周期注释、Rc<dyn ...>,我不得不在多个地方使用.clone()来克隆闭包。这里是实际的代码(问题出在Parser类型上)。从代码角度来看,它很混乱,而且我认为它会很昂贵,这就是为什么我决定重写Parser,改用fn() ...而不是Rc<dyn Fn() ...>。我走错了吗? - MaiaVictor
@MaiaVictor 如果你想要一个组合器解析器,可以看一下nom,我自己也在编写一个,但还没有发布,仍在开发中。dyn FnFn非常不同。我的代码示例不使用dyn,nom也不使用,所有都是通用的。 - Stargateur
我不喜欢 nom,所以我正在制作一个更直观的东西。 - MaiaVictor
只是一个问题,如果 dyn FnFn 不同,那么为什么 Rust 在你尝试创建类似 type UnOp = Fn(u64) -> u64 的别名时强制你写 dyn Fn?它会抱怨说“没有显式 dyn 的 trait 对象已被弃用”。 - MaiaVictor

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