如何在Rust中使用函数签名来编写一个接受函数作为参数的函数?

huangapple go评论54阅读模式
英文:

How to have a function that takes a parameter that is to a function using a function signature in Rust?

问题

你好,我在Rust中有一个函数,需要将函数签名fn(T) -> T作为第一个参数,并且y的类型为T。这是一个泛型函数。我该如何允许这个函数接受该参数?我一直遇到以下错误:

expected identifier, found keyword fn


谢谢。

我尝试添加r#来转义标识符,但没有成功。
英文:
fn test<T>(fn(T) -> T, y: T) -> T {
    return test(fn(T) -> T, fn(y));
}

Hello, I have a function here in rust that needs to take in the function signature fn(T) -> T as the first parameter, and y of type T. This is a generic function. How do I allow this function to take in that parameter? I keep getting the:

expected identifier, found keyword `fn`

error.

Thanks.

I tried to add a r# to escape the identitifer but that didnt work.

答案1

得分: 1

有几种方式,取决于你对“function”是什么含义。目前最接近你现有的是这样的。

fn test<T>(my_function: fn(T) -> T, y: T) -> T {
    ...
}

像任何参数一样,一个参数如果恰好是一个函数,必须被赋予一个名字。这个函数可以用一个函数指针作为其第一个参数来调用。也就是说,如果你在一个模块的顶层定义一个函数,或者一个简单的闭包(一个实际上没有封闭变量的闭包),那么你可以用它来调用 test。然而,你不能使用非平凡的闭包。如果你想这样做,你需要使用三个闭包特性中的一个。

FnOnce 是最强大的闭包特性。FnOnce 通过 获取其闭包值,并且只能调用一次。当它完成时,它会按值丢弃它所封闭的东西。实质上,FnOnce 可以被认为有这个签名(一个近似值,因为Rust没有可变参数的泛型):

trait FnOnce<Args...> {
  fn call(self, Args...);
}

FnMut 是次之的。它可以通过引用处理可变的闭包值,并且可以修改其值。因此,它不能同时被多次调用(就像 &mut 一样,我们只能同时有一个对其 self 的活跃引用)。可以这样认为:

trait FnMut<Args...> {
  fn call(&mut self, Args...);
}

最后,Fn 可以从任何地方调用,任意次数,并且可以在任意次数并发调用。它不能修改其闭包值。其签名基本上是:

trait Fn<Args...> {
  fn call(&self, Args...);
}

请注意,在这些示例中,唯一的区别是 self 的类型。FnOnce 通过值拥有 selfFnMut 通过 &mut self 获取,Fn 通过 &self 获取。

要使用这些中的每一个,你的签名将看起来像以下之一:

fn test<T, F: FnOnce(T) -> T>(my_function: F, y: T) -> T { ... }
fn test<T, F: FnMut(T) -> T>(my_function: F, y: T) -> T { ... }
fn test<T, F: Fn(T) -> T>(my_function: F, y: T) -> T { ... }

那么我们应该使用哪一个?你有四个选项:fn 具体类型或三个特性中的一个 FnOnceFnMutFn。这些特性在性质上是分层的。每个 Fn 都是一个 FnMut,每个 Fn 都是一个 FnOnce。此外,每个函数指针 fn 都是一个 Fn,因为空闭包永远不会被修改,因此我们永远不应该在其中需要可变状态。所以,总结一下:

  • FnOnce 是一个非常强大的保证。如果可以的话,使用它。如果你只调用函数一次,请使用这个。
  • FnMut 可以同时调用。如果你在多个线程之间 使用该函数,并且 以可能是递归的方式调用该函数(可能作为其自身执行的一部分调用自己),那么请使用 FnMut
  • Fn 在保证方面是最弱的特性。如果你不能使用上述任何一个,就使用这个。
  • fn 限制你只能使用实际的函数指针,而不能使用闭包。如果你正在编写一个类似C风格的接口或者需要FFI兼容性,那么应该使用这个,因为C不理解Rust的闭包。
英文:

There are several ways, depending on what you mean by "function". The closest to what you have right now is this.

fn test&lt;T&gt;(my_function: fn(T) -&gt; T, y: T) -&gt; T {
    ...
}

Like any argument, an argument which happens to be a function must be given a name. This function can be called with a function pointer as its first argument. That is, if you define a function at the top-level of a module or a trivial closure (one that doesn't actually close around variables), then you can use it to call test. However, you cannot use nontrivial closures. If you want to do so, you need to use one of three closure traits.

FnOnce is the strongest closure trait. An FnOnce takes its closure values by value and can be called once. When it's done, it drops the things it closed over by value. Essentially, FnOnce can be thought of as having this signature (an approximation since Rust doesn't have variadic generics):

trait FnOnce&lt;Args...&gt; {
  fn call(self, Args...);
}

FnMut is the next strongest. It can handle mutable closure values by reference and can modify its values. As such, it cannot be called multiple times concurrently (like a &amp;mut, we can only have one active reference to its self at a time). It can be thought of like so.

trait FnMut&lt;Args...&gt; {
  fn call(&amp;mut self, Args...);
}

Finally, Fn can be called from anywhere, any number of times, and any number of times concurrently. It cannot modify its closure values. Its signature is basically

trait Fn&lt;Args...&gt; {
  fn call(&amp;self, Args...);
}

Note that in these examples, the only difference is the type of self. FnOnce has self by value, FnMut takes &amp;mut self, and Fn takes &amp;self.

To use each of these, your signature would look like one of the following

fn test&lt;T, F: FnOnce(T) -&gt; T&gt;(my_function: F, y: T) -&gt; T { ... }
fn test&lt;T, F: FnMut(T) -&gt; T&gt;(my_function: F, y: T) -&gt; T { ... }
fn test&lt;T, F: Fn(T) -&gt; T&gt;(my_function: F, y: T) -&gt; T { ... }

So which one should we use? You have four options: The fn concrete type or one of the three traits FnOnce, FnMut, or Fn. The traits are hierarchical in nature. Every Fn is an FnMut, and every Fn is an FnOnce. Additionally, every function pointer fn is a Fn, since the empty closure can never be modified and hence we should never need mutable state inside it. So, to summarize,

  • FnOnce is an incredibly powerful guarantee. Use it if you can. If you're only calling the function once, use this one.
  • FnMut can be called concurrently. If you're not using the function across multiple threads and are not calling the function in a way that may be recursive (may call itself as part of its own execution), then use FnMut.
  • Fn is the weakest trait in terms of guarantees. Use this one if you can't use either of the above.
  • fn restricts you to actual function pointers, not closures. This should be used if you're writing a C-style interface or need FFI compatibility, since C doesn't understand Rust closures.

huangapple
  • 本文由 发表于 2023年4月4日 03:14:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/75923018.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定