I'm coming from a C++ background. A C++ template is compiled for each unique instance of the template's type(s).
template <typename T>
T add(T a, T b) {
return a + b;
}
...
int intResult = add(3, 5); // the function was compiled for int specialization
double doubleResult = add(3.5, 2.1); // the function was compiled for double specialization
On to rust. For something like this (a function which takes a closure as an argument):
pub fn copy_checked<R1, R2>(&'sdl mut self, path: &str, src: R1, dst: R2) -> Result<(), String>
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
RenderSystem::get_or_load_and_use( // closure arg here
|txt: &Texture<'_>| self.canvas.copy(&txt, src, dst),
)
}
The above fails because R1 and R2 also require the copy trait.
cannot move out of `dst`, a captured variable in an `FnMut` closure
move occurs because `dst` has type `R2`, which does not implement the `Copy` trait
This has made me uncertain on how exactly rust templates are implemented. If it was like C++, then there should be no need to copy the args. The function would just be inlined compiled in. But if it's doing a copy, that means that different lambdas with the same signature (args and return type) wouldn't need to have different compiled instances. They could have the same function body, but only a different function pointer is used.
In summary: Do closure template parameters with the same prototype (args and return type) but different body count as different types for monomorphization by the rust compiler?
I'm coming from a C++ background. A C++ template is compiled for each unique instance of the template's type(s).
template <typename T>
T add(T a, T b) {
return a + b;
}
...
int intResult = add(3, 5); // the function was compiled for int specialization
double doubleResult = add(3.5, 2.1); // the function was compiled for double specialization
On to rust. For something like this (a function which takes a closure as an argument):
pub fn copy_checked<R1, R2>(&'sdl mut self, path: &str, src: R1, dst: R2) -> Result<(), String>
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
RenderSystem::get_or_load_and_use( // closure arg here
|txt: &Texture<'_>| self.canvas.copy(&txt, src, dst),
)
}
The above fails because R1 and R2 also require the copy trait.
cannot move out of `dst`, a captured variable in an `FnMut` closure
move occurs because `dst` has type `R2`, which does not implement the `Copy` trait
This has made me uncertain on how exactly rust templates are implemented. If it was like C++, then there should be no need to copy the args. The function would just be inlined compiled in. But if it's doing a copy, that means that different lambdas with the same signature (args and return type) wouldn't need to have different compiled instances. They could have the same function body, but only a different function pointer is used.
In summary: Do closure template parameters with the same prototype (args and return type) but different body count as different types for monomorphization by the rust compiler?
Share Improve this question edited Feb 1 at 21:07 cafce25 27.6k5 gold badges45 silver badges58 bronze badges asked Feb 1 at 20:49 jagprog5jagprog5 1311 silver badge6 bronze badges 3 |2 Answers
Reset to default 2Your question touches on a number of interrelated things:
Each closure definition creates a unique anonymous type even with the same signature.
The Rust compiler does use monomorphization to compile generics - meaning calling a function with different closures will compile separate functions for each unique closure that is passed.
Rust does not wait until monomorphization to return errors. In fact a goal is that there are no errors at monomorphization time. Instead all constraints are checked up front which must hold for all cases. This is the biggest different between C++ templates and Rust generics.
So even if you pass types for R1
and R2
that do implement Copy
and would theoretically pass monomorphization, you don't constrain the generic types R1
and R2
to be Copy
and thus the function body doesn't pass the constraint solver.
The Copy
constraint is not due to monomorphization, but due to ownership. You're passing src
and dst
into .copy
by value meaning you lose ownership after passing them. But the error indicates the closure passed to get_or_load_and_use
is FnMut
- which can be called multiple times and thus .copy
may be called multiple times. The only way you can pass ownership of something multiple times is if it implements Copy
. So for this to compile, you need to add a constraint that R1
and R2
are Copy
:
where
R1: Into<Option<Rect>> + Copy,
R2: Into<Option<Rect>> + Copy,
Or if Copy
is too restrictive. You can instead constrain on Clone
and pass clones to .copy
:
self.canvas.copy(&txt, src.clone(), dst.clone())
See What is the difference between Copy and Clone?
Let me fully reproduce your error, hopefully matching your problem:
pub struct Rect;
struct Texture;
fn canvas_copy<R1, R2>(_txt: &Texture, _src: R1, _dst: R2)
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
}
fn get_or_load_and_use<F>(mut f: F) -> Result<(), String>
where
F: FnMut(&Texture),
{
let txt = Texture;
f(&txt);
Ok(())
}
pub fn copy_checked<R1, R2>(src: R1, dst: R2) -> Result<(), String>
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
get_or_load_and_use(
// closure arg here
|txt: &Texture| canvas_copy(&txt, src, dst),
)
}
error[E0507]: cannot move out of `src`, a captured variable in an `FnMut` closure
--> src\lib.rs:27:43
|
20 | pub fn copy_checked<R1, R2>(src: R1, dst: R2) -> Result<(), String>
| --- captured outer variable
...
27 | |txt: &Texture| canvas_copy(&txt, src, dst),
| --------------- ^^^ move occurs because `src` has type `R1`, which does not implement the `Copy` trait
| |
| captured by this `FnMut` closure
|
help: if `R1` implemented `Clone`, you could clone the value
--> src\lib.rs:20:21
|
20 | pub fn copy_checked<R1, R2>(src: R1, dst: R2) -> Result<(), String>
| ^^ consider constraining this type parameter with `Clone`
...
27 | |txt: &Texture| canvas_copy(&txt, src, dst),
| --- you could clone this value
error[E0507]: cannot move out of `dst`, a captured variable in an `FnMut` closure
--> src\lib.rs:27:48
|
20 | pub fn copy_checked<R1, R2>(src: R1, dst: R2) -> Result<(), String>
| --- captured outer variable
...
27 | |txt: &Texture| canvas_copy(&txt, src, dst),
| --------------- ^^^ move occurs because `dst` has type `R2`, which does not implement the `Copy` trait
| |
| captured by this `FnMut` closure
|
help: if `R2` implemented `Clone`, you could clone the value
--> src\lib.rs:20:25
|
20 | pub fn copy_checked<R1, R2>(src: R1, dst: R2) -> Result<(), String>
| ^^ consider constraining this type parameter with `Clone`
...
27 | |txt: &Texture| canvas_copy(&txt, src, dst),
| --- you could clone this value
The reason this fails has actually nothing to do with how the Rust compiler resolves different closures, but instead the problem is the FnMut
. You take src
and dst
in your canvas.copy
as values, so you can only call canvas.copy
once. Yet you specify that your closure is FnMut
, meaning it can be called multiple times. A closure that consumes a captured value does not implement FnMut
, as outlined in another answer of mine here.
So to fix your code, you have several options:
- Specify the closure type as
FnOnce
:
pub struct Rect;
struct Texture;
fn canvas_copy<R1, R2>(_txt: &Texture, _src: R1, _dst: R2)
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
}
fn get_or_load_and_use<F>(f: F) -> Result<(), String>
where
F: FnOnce(&Texture),
{
let txt = Texture;
f(&txt);
Ok(())
}
pub fn copy_checked<R1, R2>(src: R1, dst: R2) -> Result<(), String>
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
get_or_load_and_use(
// closure arg here
|txt: &Texture| canvas_copy(&txt, src, dst),
)
}
- Make
src
anddst
clonable and clone them:
pub struct Rect;
struct Texture;
fn canvas_copy<R1, R2>(_txt: &Texture, _src: R1, _dst: R2)
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
}
fn get_or_load_and_use<F>(mut f: F) -> Result<(), String>
where
F: FnMut(&Texture),
{
let txt = Texture;
f(&txt);
Ok(())
}
pub fn copy_checked<R1, R2>(src: R1, dst: R2) -> Result<(), String>
where
R1: Into<Option<Rect>> + Clone,
R2: Into<Option<Rect>> + Clone,
{
get_or_load_and_use(
// closure arg here
|txt: &Texture| canvas_copy(&txt, src.clone(), dst.clone()),
)
}
- Make
src
anddst
Copy
, which will change how functions are called. Function arguments withCopy
will no longer be pass-by-move, but instead be pass-by-copy:
pub struct Rect;
struct Texture;
fn canvas_copy<R1, R2>(_txt: &Texture, _src: R1, _dst: R2)
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
}
fn get_or_load_and_use<F>(mut f: F) -> Result<(), String>
where
F: FnMut(&Texture),
{
let txt = Texture;
f(&txt);
Ok(())
}
pub fn copy_checked<R1, R2>(src: R1, dst: R2) -> Result<(), String>
where
R1: Into<Option<Rect>> + Copy,
R2: Into<Option<Rect>> + Copy,
{
get_or_load_and_use(
// closure arg here
|txt: &Texture| canvas_copy(&txt, src, dst),
)
}
I would probably go with the FnOnce
version, because if you only call the function once, there's no reason to use a more restrictive type. Some recommendations regarding this can be found in this answer of mine.
The FnOnce
is probably also what you had in mind when you made the statement:
If it was like C++, then there should be no need to copy the args. The function would just be inlined compiled in.
This is exactly how FnOnce
behaves in this situation.
Another solution would be to change the signature of canvas.copy
, although it's unclear if that's possible or not; it seems from your code like this could be an external library function.
If you can, you can resolve all of this by simply taking the values by reference instead:
pub struct Rect;
struct Texture;
fn canvas_copy<R1, R2>(_txt: &Texture, _src: &R1, _dst: &R2)
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
}
fn get_or_load_and_use<F>(mut f: F) -> Result<(), String>
where
F: FnMut(&Texture),
{
let txt = Texture;
f(&txt);
Ok(())
}
pub fn copy_checked<R1, R2>(src: &R1, dst: &R2) -> Result<(), String>
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
get_or_load_and_use(
// closure arg here
|txt: &Texture| canvas_copy(&txt, src, dst),
)
}
Which, in my opinion, would be even cleaner. Still, I would make the FnMut
an FnOnce
if you only call it once; that's just good API design:
pub struct Rect;
struct Texture;
fn canvas_copy<R1, R2>(_txt: &Texture, _src: &R1, _dst: &R2)
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
}
fn get_or_load_and_use<F>(f: F) -> Result<(), String>
where
F: FnOnce(&Texture),
{
let txt = Texture;
f(&txt);
Ok(())
}
pub fn copy_checked<R1, R2>(src: &R1, dst: &R2) -> Result<(), String>
where
R1: Into<Option<Rect>>,
R2: Into<Option<Rect>>,
{
get_or_load_and_use(
// closure arg here
|txt: &Texture| canvas_copy(&txt, src, dst),
)
}
src
anddst
into.copy
by value meaning you lose ownership after passing them. But the error indicates the closure passed toget_or_load_and_use
isFnMut
- which implies that it can be called multiple times and thus.copy
may be called multiple times. The only way you can pass ownership of something multiple times is if it implementsCopy
. – kmdreko Commented Feb 1 at 21:01add
in your C++ code performs is optimization dependent. In Rust no clone is performed unless explicitly requested. That on the contrary means every object in Rust is trivially movable, whilst in C/C++ you need a move constructor. Which has some other implications like: self-referential structs in Rust are impossible. Irrelevant to your question though. – Finomnis Commented Feb 2 at 8:58