Rust의 관용적 콜백
C / C ++에서는 일반적으로 일반 함수 포인터로 콜백을 수행하며 void* userdata
매개 변수도 전달할 수 있습니다. 이 같은:
typedef void (*Callback)();
class Processor
{
public:
void setCallback(Callback c)
{
mCallback = c;
}
void processEvents()
{
for (...)
{
...
mCallback();
}
}
private:
Callback mCallback;
};
Rust에서 이것을하는 관용적 방법은 무엇입니까? 특히 내 setCallback()
함수 는 어떤 유형을 취해야하며 어떤 유형이어야 mCallback
합니까? 걸릴 Fn
까요? 어쩌면 FnMut
? 저장 Boxed
합니까? 예는 놀랍습니다.
짧은 답변 : 유연성을 극대화하기 위해 FnMut
콜백 유형에 대한 콜백 setter 일반을 사용하여 콜백을 박스형 객체 로 저장할 수 있습니다 . 이에 대한 코드는 답변의 마지막 예에 나와 있습니다. 자세한 설명은 계속 읽으십시오.
"함수 포인터": 콜백 fn
질문의 C ++ 코드와 가장 가까운 것은 콜백을 fn
유형 으로 선언하는 것 입니다. C ++의 함수 포인터처럼 키워드로 fn
정의 된 함수를 캡슐화합니다 fn
.
type Callback = fn();
struct Processor {
callback: Callback,
}
impl Processor {
fn set_callback(&mut self, c: Callback) {
self.callback = c;
}
fn process_events(&self) {
(self.callback)();
}
}
fn simple_callback() {
println!("hello world!");
}
fn main() {
let mut p = Processor { callback: simple_callback };
p.process_events(); // hello world!
}
이 코드 Option<Box<Any>>
는 함수와 관련된 "사용자 데이터"를 보유하기 위해 를 포함하도록 확장 될 수 있습니다 . 그렇다하더라도 그것은 관용적 인 Rust가 아닐 것입니다. 데이터를 함수와 연관시키는 Rust 방식 은 최신 C ++에서와 같이 익명의 클로저로 데이터를 캡처하는 것입니다 . 폐쇄가 아니기 때문에 fn
, set_callback
함수 객체의 다른 종류에 동의해야합니다.
일반 함수 객체로서의 콜백
Rust와 C ++ 클로저에서 동일한 호출 서명을 가진 클로저는 클로저 객체에 저장하는 캡처 된 값의 다른 크기를 수용하기 위해 다른 크기로 제공됩니다. 또한 각 클로저 사이트는 컴파일 타임에 클로저 객체의 유형 인 고유 한 익명 유형을 생성합니다. 이러한 제약으로 인해 구조체는 이름 또는 유형 별칭으로 콜백 유형을 참조 할 수 없습니다.
구체적인 유형을 참조하지 않고 구조체에서 클로저를 소유하는 한 가지 방법은 구조체를 generic 으로 만드는 것 입니다. 구조체는 전달하는 구체적인 함수 또는 클로저에 대한 콜백 유형과 크기를 자동으로 조정합니다.
struct Processor<CB> where CB: FnMut() {
callback: CB,
}
impl<CB> Processor<CB> where CB: FnMut() {
fn set_callback(&mut self, c: CB) {
self.callback = c;
}
fn process_events(&mut self) {
(self.callback)();
}
}
fn main() {
let s = "world!".to_string();
let callback = || println!("hello {}", s);
let mut p = Processor { callback: callback };
p.process_events();
}
As before, the new definition of callback will be able to accept top-level functions defined with fn
, but this one will also accept closures as || println!("hello world!")
, as well as closures that capture values, such as || println!("{}", somevar)
. Because of this the closure doesn't need a separate userdata
argument; it can simply capture the data from its environment and it will be available when it is called.
But what's the deal with the FnMut
, why not just Fn
? Since closures hold captured values, Rust enforces the same rules on them that it enforces on other container objects. Depending on what the closures do with the values they hold, they are grouped in three families, each marked with a trait:
Fn
are closures that only read data, and may be safely called multiple times, possibly from multiple threads. Both above closures areFn
.FnMut
are closures that modify data, e.g. by writing to a capturedmut
variable. They may also be called multiple times, but not in parallel. (Calling aFnMut
closure from multiple threads would lead to a data race, so it can only be done with the protection of a mutex.) The closure object must be declared mutable by the caller.FnOnce
are closures that consume the data they capture, e.g. by moving it to a function that owns them. As the name implies, these may be called only once, and the caller must own them.
Somewhat counter-intuitively, when specifying a trait bound for the type of an object that accepts a closure, FnOnce
is actually the most permissive one. Declaring that a generic callback type must satisfy the FnOnce
trait means that it will accept literally any closure. But that comes with a price: it means the holder is only allowed to call it once. Since process_events()
may opt to invoke the callback multiple times, and as the method itself may be called more than once, the next most permissive bound is FnMut
. Note that we had to mark process_events
as mutating self
.
Non-generic callbacks: function trait objects
Even though the generic implementation of the callback is extremely efficient, it has serious interface limitations. It requires each Processor
instance to be parameterized with a concrete callback type, which means that a single Processor
can only deal with a single callback type. Given that each closure has a distinct type, the generic Processor
cannot handle proc.set_callback(|| println!("hello"))
followed by proc.set_callback(|| println!("world"))
. Extending the struct to support two callbacks fields would require the whole struct to be parameterized to two types, which would quickly become unwieldy as the number of callbacks grows. Adding more type parameters wouldn't work if the number of callbacks needed to be dynamic, e.g. to implement an add_callback
function that maintains a vector of different callbacks.
To remove the type parameter, we can take advantage of trait objects, the feature of Rust that allows automatic creation of dynamic interfaces based on traits. This is sometimes referred to as type erasure and is a popular technique in C++[1][2], not to be confused with Java and FP languages' somewhat different use of the term. Readers familiar with C++ will recognize the distinction between a closure that implements Fn
and an Fn
trait object as equivalent to the distinction between general function objects and std::function
values in C++.
A trait object is created by borrowing an object with the &
operator and casting or coercing it to a reference to the specific trait. In this case, since Processor
needs to own the callback object, we cannot use borrowing, but must store the callback in a heap-allocated Box<Trait>
(the Rust equivalent of std::unique_ptr
), which is functionally equivalent to a trait object.
If Processor
stores Box<FnMut()>
, it no longer needs to be generic, but the set_callback
method is now generic, so it can properly box whatever callable you give it before storing the box in the Processor
. The callback can be of any kind as long as it doesn't consume the captured values. set_callback
being generic doesn't incur limitations discussed above, as it doesn't affect the interface of the data stored in the struct.
struct Processor {
callback: Box<FnMut()>,
}
impl Processor {
fn set_callback<CB: 'static + FnMut()>(&mut self, c: CB) {
self.callback = Box::new(c);
}
fn process_events(&mut self) {
(self.callback)();
}
}
fn simple_callback() {
println!("hello");
}
fn main() {
let mut p = Processor { callback: Box::new(simple_callback) };
p.process_events();
let s = "world!".to_string();
let callback2 = move || println!("hello {}", s);
p.set_callback(callback2);
p.process_events();
}
참고URL : https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust
'UFO ET IT' 카테고리의 다른 글
새로 고침과 플러시 (0) | 2020.11.07 |
---|---|
기본값 유형이 속성 유형과 일치하지 않습니다. (0) | 2020.11.07 |
JSON 객체를 버퍼로, 버퍼를 JSON 객체로 다시 변환 (0) | 2020.11.07 |
파일 또는 어셈블리 'Microsoft.CodeAnalysis, 버전 = 1.3.1.0, Culture = neutral, PublicKeyToken = 31bf3856ad364e35'또는 해당 종속성 중 하나를로드 할 수 없습니다. (0) | 2020.11.07 |
Java의 원시 문자열-특히 정규식 용 (0) | 2020.11.07 |