Rust 함수를 매개 변수로 어떻게 전달합니까?


89

함수를 매개 변수로 전달할 수 있습니까? 그렇지 않다면 좋은 대안은 무엇입니까?

몇 가지 다른 구문을 시도했지만 올바른 구문을 찾지 못했습니다. 나는 이것을 할 수 있다는 것을 안다.

fn example() {
    let fun: fn(value: i32) -> i32;
    fun = fun_test;
    fun(5i32);
}

fn fun_test(value: i32) -> i32 {
    println!("{}", value);
    value
}

하지만 다른 함수에 매개 변수로 함수를 전달하지 않습니다.

fn fun_test(value: i32, (some_function_prototype)) -> i32 {
    println!("{}", value);
    value
}

답변:


123

물론 넌 할 수있어:

fn fun_test(value: i32, f: &dyn Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}

fn times2(value: i32) -> i32 {
    2 * value
}

fn main() {
    fun_test(5, &times2);
}

이것은 Rust이기 때문에 클로저소유권과 수명 을 고려해야합니다 .

TL; DR; 기본적으로 3 가지 유형의 클로저 (호출 가능한 객체)가 있습니다.

  1. Fn: 캡처 한 개체를 수정할 수 없습니다.
  2. FnMut: 캡처 한 객체를 수정할 수 있습니다.
  3. FnOnce: 가장 제한적입니다. 호출 될 때 자체 및 캡처를 소비하므로 한 번만 호출 할 수 있습니다.

클로저는 Fn, FnMut 및 FnOnce를 언제 구현합니까?를 참조하십시오 . 상세 사항은

클로저와 같은 간단한 함수 포인터를 사용하는 경우 캡처 세트가 비어 있고 Fn맛 이 있습니다.

더 멋진 일을하고 싶다면 람다 함수를 사용해야합니다.

Rust에는 C 에서처럼 작동하는 함수에 대한 적절한 포인터가 있습니다. 그 유형은 예를 들어 fn(i32) -> i32. Fn(i32) -> i32, FnMut(i32) -> i32그리고 FnOnce(i32) -> i32실제로 특징이다. 함수에 대한 포인터는 항상이 세 가지를 모두 구현하지만, Rust에는 함수에 대한 포인터 (캡처 세트가 비어 있는지에 따라)로 변환되거나 변환되지 않을 수도있는 클로저도 있지만 이러한 특성 중 일부를 구현합니다.

예를 들어 위의 예를 확장 할 수 있습니다.

fn fun_test_impl(value: i32, f: impl Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}
fn fun_test_dyn(value: i32, f: &dyn Fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}
fn fun_test_ptr(value: i32, f: fn(i32) -> i32) -> i32 {
    println!("{}", f(value));
    value
}

fn times2(value: i32) -> i32 {
    2 * value
}

fn main() {
    let y = 2;
    //static dispatch
    fun_test_impl(5, times2);
    fun_test_impl(5, |x| 2*x);
    fun_test_impl(5, |x| y*x);
    //dynamic dispatch
    fun_test_dyn(5, &times2);
    fun_test_dyn(5, &|x| 2*x);
    fun_test_dyn(5, &|x| y*x);
    //C-like pointer to function
    fun_test_ptr(5, times2);
    fun_test_ptr(5, |x| 2*x); //ok: empty capture set
    fun_test_ptr(5, |x| y*x); //error: expected fn pointer, found closure
}

1
두 작품을 <F : Fn ...> 사용 여부 (.., f : & Fn ...) 사용에 차이가 있습니다. 알아야 할 세부 사항이 있습니까?
Angel Angel

@AngelAngel : 글쎄요, Fn*특성 이니 , 보통 <T: Trait>(t: &T)적용됩니다. 비 제네릭 솔루션의 주요 제한 사항은 참조와 함께 사용해야한다는 것입니다. 따라서 FnOnce복사본으로 전달되어야 하는을 원하면 일반 스타일을 사용해야합니다.
rodrigo 2011

5
대신 특성의 제네릭을 사용하는 것이 더 관용적이다 참고 (즉, 객체 <F: Fn..>대신에 (f: &Fn...)그리고이 이유입니다 -. 특성 객체는 동적 파견을 요구하면서 제네릭, 정적 디스패치가 발생합니다.
블라디미르 Matveev

3
흥미롭게도 인터페이스 (호출자) 관점에서 보면 FnOnce실제로 가장 일반적인 특성입니다. 캡처 된 상태의 읽기, 수정 또는 소유권에 관계없이 모든 클로저를 허용합니다. FnMut더 제한적이며 캡처 된 객체의 소유권을 갖는 클로저를 허용하지 않습니다 (그러나 여전히 상태 수정을 허용합니다). Fn캡처 된 상태를 수정하는 클로저를 허용하지 않기 때문에 가장 제한적입니다. 따라서 호출자 &Fn에게 가장 큰 제한을 두는 동시에 funTest호출자 f내부에서 호출 할 수있는 방법에 대한 제한을 최소화 해야합니다.
user4815162342

30

Fn, FnMutFnOnce다른 대답 설명이다 폐쇄 유형. 범위에서 닫히는 함수 유형입니다.

클로저를 전달하는 것 외에도 Rust는 다음과 같은 간단한 (비 클로저) 함수 전달도 지원합니다.

fn times2(value: i32) -> i32 {
    2 * value
}

fn fun_test(value: i32, f: fn(i32) -> i32) -> i32 {
    println!("{}", f (value));
    value
}

fn main() {
    fun_test (2, times2);
}

fn(i32) -> i32여기에 함수 포인터 유형이 있습니다.

함수 유형으로 작업하는 것보다 완전한 클로저가 필요하지 않은 경우 이러한 클로저 수명 nicities를 처리 할 필요가 없기 때문에 종종 더 간단합니다.


이것은 구조체의 메서드와 함께 작동합니까?
Ivan Temchenko

@IvanTemchenko 어쩌면? 다음은 플레이 할 수있는 몇 가지 코드입니다. play.rust-lang.org/…
ArtemGr

그것은 정확히 내가 의미하는 것이 아닙니다 =) 자신의 상태를 캡처하는 dyn 클로저를 반환하는 해결 방법을 찾았으므로 인스턴스 참조를 전달할 필요가 없습니다 ...
이반 Temchenko에게
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.