Generic math in Rust without Copy (std::ops::Add, etc.) - rust

As part of a "learn Rust" project, I've been working through some Project Euler problems, in which it would be convenient to have a few generic math functions. As an example, let's say I want to write a generic "square" function. If I can live with the builtin numeric types (all of which I believe are Copy), I can write this:
fn square<A>(n: A) -> A
where
A: Mul<Output = A> + Copy,
{
n.mul(n)
}
This seems to work fine. But what if I want to use a numeric type that is not Copy? Say I'm using a bignum library in which numbers are not Copy, but which implements std::ops::Mul. I would have thought I could do this:
fn square_ref<'a, A>(n: &'a A) -> A
where
A: Mul<&'a A, Output = A>,
{
n.mul(n)
}
but that gets me the following error:
error[E0507]: cannot move out of `*n` which is behind a shared reference
--> src/main.rs:16:5
|
16 | n.mul(n)
| ^ move occurs because `*n` has type `A`, which does not implement the `Copy` trait
error: aborting due to previous error
Why does the call to mul insist on resolving to type A, instead of &A? Just to be clear, my question isn't really about generic math in Rust -- I'm hoping that figuring out how to do this sort of thing, or learning why it can't be done, will help me better understand the language in general.

You need to bound &'a A to be Mul, not A:
use std::ops::Mul;
fn square_ref<A>(n: &A) -> A
where
// note the &'a A instead of just A
for<'a> &'a A: Mul<&'a A, Output = A>,
{
n.mul(n)
}

Related

Problem with value arguments in const fn with basic numeric generics

I'm trying to translate some of my C++ code into Rust and came across the following problem. The code is simplified to a (hopefully) minimal not-working example. In the end it is supposed to work with all unsigned integer types, but for this example it just needs to implement the PartialOrd trait.
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]
#![feature(const_trait_impl)]
const fn foo<T>(n: T, k: T) -> T
where
T: Sized,
T: Copy,
T: ~const core::marker::Destruct,
T: ~const std::cmp::PartialOrd,
{
if n < k {
n
} else {
k
}
}
Fails to compile with the following error message:
error[E0658]: cannot borrow here, since the borrowed element may contain interior mutability
13 | if n < k {
| ^
If I replace the arguments n and k with references, it compiles. Turns out, the functions in the PartialOrd are also implemented using references. So from my understanding, the expression 2 < 4 will call the le function with references. For performance reasons, I doubt that's what's happening, though.
It's actually two questions that I'm asking:
Can I solve the error without using references?
Why is PartialOrd using references while Ord uses values (at least for min and max, but not for cmp)?
Adding #![feature(const_refs_to_cell)] makes it compile.
min and max take Self because they return one of the two arguments as Self. Other methods in Ord and PartialOrd else returns a bool or an Ordering so they can take by reference. if min and max they took by reference the signature would be fn max<'a>(&'a self, other: &'a Self) -> &'a Self which you "get for free" because there is a impl<A> Ord for &A where A: Ord.

Why isn't `std::mem::drop` exactly the same as the closure |_|() in higher-ranked trait bounds?

The implementation of std::mem::drop is documented to be the following:
pub fn drop<T>(_x: T) { }
As such, I would expect the closure |_| () (colloquially known as the toilet closure) to be a potential 1:1 replacement to drop, in both directions. However, the code below shows that drop isn't compatible with a higher ranked trait bound on the function's parameter, whereas the toilet closure is.
fn foo<F, T>(f: F, x: T)
where
for<'a> F: FnOnce(&'a T),
{
dbg!(f(&x));
}
fn main() {
foo(|_| (), "toilet closure"); // this compiles
foo(drop, "drop"); // this does not!
}
The compiler's error message:
error[E0631]: type mismatch in function arguments
--> src/main.rs:10:5
|
1 | fn foo<F, T>(f: F, x: T)
| ---
2 | where
3 | for<'a> F: FnOnce(&'a T),
| ------------- required by this bound in `foo`
...
10 | foo(drop, "drop"); // this does not!
| ^^^
| |
| expected signature of `for<'a> fn(&'a _) -> _`
| found signature of `fn(_) -> _`
error[E0271]: type mismatch resolving `for<'a> <fn(_) {std::mem::drop::<_>} as std::ops::FnOnce<(&'a _,)>>::Output == ()`
--> src/main.rs:10:5
|
1 | fn foo<F, T>(f: F, x: T)
| ---
2 | where
3 | for<'a> F: FnOnce(&'a T),
| ------------- required by this bound in `foo`
...
10 | foo(drop, "drop"); // this does not!
| ^^^ expected bound lifetime parameter 'a, found concrete lifetime
Considering that drop is supposedly generic with respect to any sized T, it sounds unreasonable that the "more generic" signature fn(_) -> _ is not compatible with for<'a> fn (&'a _) -> _. Why is the compiler not admitting the signature of drop here, and what makes it different when the toilet closure is placed in its stead?
The core of the issue is that drop is not a single function, but rather a parameterized set of functions that each drop some particular type. To satisfy a higher-ranked trait bound (hereafter hrtb), you'd need a single function that can simultaneously take references to a type with any given lifetime.
We'll use drop as our typical example of a generic function, but all this applies more generally too. Here's the code for reference: fn drop<T>(_: T) {}.
Conceptually, drop is not a single function, but rather one function for every possible type T. Any particular instance of drop takes only arguments of a single type. This is called monomorphization. If a different T is used with drop, a different version of drop is compiled. That's why you can't pass a generic function as an argument and use that function in full generality (see this question)
On the other hand, a function like fn pass(x: &i32) -> &i32 {x} satisfies the hrtb for<'a> Fn(&'a i32) -> &'a i32. Unlike drop, we have a single function that simultaneously satisfies Fn(&'a i32) -> &'a i32 for every lifetime 'a. This is reflected in how pass can be used.
fn pass(x: &i32) -> &i32 {
x
}
fn two_uses<F>(f: F)
where
for<'a> F: Fn(&'a i32) -> &'a i32, // By the way, this can simply be written
// F: Fn(&i32) -> &i32 due to lifetime elision rules.
// That applies to your original example too.
{
{
// x has some lifetime 'a
let x = &22;
println!("{}", f(x));
// 'a ends around here
}
{
// y has some lifetime 'b
let y = &23;
println!("{}", f(y));
// 'b ends around here
}
// 'a and 'b are unrelated since they have no overlap
}
fn main() {
two_uses(pass);
}
(playground)
In the example, the lifetimes 'a and 'b have no relation to each other: neither completely encompasses the other. So there isn't some kind of subtyping thing going on here. A single instance of pass is really being used with two different, unrelated lifetimes.
This is why drop doesn't satisfy for<'a> FnOnce(&'a T). Any particular instance of drop can only cover one lifetime (ignoring subtyping). If we passed drop into two_uses from the example above (with slight signature changes and assuming the compiler let us), it would have to choose some particular lifetime 'a and the instance of drop in the scope of two_uses would be Fn(&'a i32) for some concrete lifetime 'a. Since the function would only apply to single lifetime 'a, it wouldn't be possible to use it with two unrelated lifetimes.
So why does the toilet closure get a hrtb? When inferring the type for a closure, if the expected type hints that a higher-ranked trait bound is needed, the compiler will try to make one fit. In this case, it succeeds.
Issue #41078 is closely related to this and in particular, eddyb's comment here gives essentially the explanation above (though in the context of closures, rather than ordinary functions). The issue itself doesn't address the present problem though. It instead addresses what happens if you assign the toilet closure to a variable before using it (try it out!).
It's possible that the situation will change in the future, but it would require a pretty big change in how generic functions are monomorphized.
In short, both lines should fail. But since one step in old way of handling hrtb lifetimes, namely the leak check, currently has some soundness issue, rustc ends up (incorrectly) accepting one and leaving the other with a pretty bad error message.
If you disable the leak check with rustc +nightly -Zno-leak-check, you'll be able to see a more sensible error message:
error[E0308]: mismatched types
--> src/main.rs:10:5
|
10 | foo(drop, "drop");
| ^^^ one type is more general than the other
|
= note: expected type `std::ops::FnOnce<(&'a &str,)>`
found type `std::ops::FnOnce<(&&str,)>`
My interpretation of this error is that the &x in the body of the foo function only has a scope lifetime confined to the said body, so f(&x) also has the same scope lifetime which can't possibly satisfy the for<'a> universal quantification required by the trait bound.
The question you present here is almost identical to issue #57642, which also has two contrasting parts.
The new way to process hrtb lifetimes is by using so-called universes. Niko has a WIP to tackle the leak check with universes. Under this new regime, both parts of issue #57642 linked above is said to all fail with far more clear diagnoses. I suppose the compiler should be able to handle your example code correctly by then, too.

What is the proper way to coerce an iterator to return a value instead of a reference (or vice versa)?

The general setup is I have an array of values I'd like to map() and then chain() with 1 additional value. I've learned from this answer that the proper way to construct that final value is to use std::iter::once. This works and eliminated the below problem, but I would still like to understand it better.
In my broken, likely rust-anti-pattern-riddled example, I was using an array of a single element and then calling into_iter(). This produced a value / reference type-mismatch in the chain.
Question: What is the Rust-idiomatic mechanism for correcting this value / reference mismatch? Particularly if clone and copy are unavailable.
Background: Why is there a type mis-match to begin with?
This much I believe I understand. Based on the definition of std::iter::Map, the item type for the iterator is type Item = B where B is constrained by F: FnMut(<I as Iterator>::Item) -> B (i.e. the mapped type). However array defines the following 2 IntoIterator implementations, both of which appear to produce references.
impl<'a, const N: usize, T> IntoIterator for &'a [T; N] where
[T; N]: LengthAtMost32,
type Item = &'a T
impl<'a, const N: usize, T> IntoIterator for &'a mut [T; N] where
[T; N]: LengthAtMost32,
type Item = &'a mut T
Example demonstrating the issue:
#[derive(PartialEq, Eq, Clone, Copy)]
enum Enum1 {
A, B, C
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum Enum2 {
X, Y, Z
}
struct Data {
// Other data omitted
e1: Enum1,
e2: Enum2
}
struct Consumer {
// Other data omitted
/** Predicate which evaluates if this consumer can consume given Data */
consumes: Box<dyn Fn(&Data) -> bool>
}
fn main() {
// Objective: 3 consumers which consume data with A, B, and X respectively
let v: Vec<Consumer> = [Enum1::A, Enum1::B].iter()
.map(|&e1| Consumer { consumes: Box::new(move |data| data.e1 == e1) })
// This chain results in an iterator type-mismatch:
// expected &Consumer, found Consumer
.chain([Consumer { consumes: Box::new(move |data| data.e2 == Enum2::X) }].into_iter())
.collect(); // Fails as well due to the chain failure
}
Error:
error[E0271]: type mismatch resolving `<std::slice::Iter<'_, Consumer> as std::iter::IntoIterator>::Item == Consumer`
--> src/main.rs:52:10
|
52 | .chain([Consumer { consumes: Box::new(move |data| data.e2 == Enum2::X) }].into_iter())
| ^^^^^ expected reference, found struct `Consumer`
|
= note: expected type `&Consumer`
found type `Consumer`
Rust playground example.
There is a long-standing issue regarding this. The technical details are a bit heavy, but essentially, due to underlying, technical reasons, you cannot take ownership of a fixed-size array and return owned references without a lot of hocus pocus. This becomes obvious when you think about what a fixed-size array is and how it is stored in memory, and how you can get elements out without cloning them.
As a result, due to the implementations you found already, you can only get borrowed references. You can bypass this with arrayvec (as they have a sound implementation of IntoIterator for ArrayVec with owned types), or you can require that all your T: Clone and deal with it that way, at a cost of extra items in memory (temporarily; 90% of the time the compiler optimizes this away).

How do I solve the error "the precise format of `Fn`-family traits' type parameters is subject to change"?

I have written a problem solver in Rust which as a subroutine needs to make calls to a function which is given as a black box (essentially I would like to give an argument of type Fn(f64) -> f64).
Essentially I have a function defined as fn solve<F>(f: F) where F : Fn(f64) -> f64 { ... } which means that I can call solve like this:
solve(|x| x);
What I would like to do is to pass a more complex function to the solver, i.e. a function which depends on multiple parameters etc.
I would like to be able to pass a struct with a suitable trait implementation to the solver. I tried the following:
struct Test;
impl Fn<(f64,)> for Test {}
This yield the following error:
error: the precise format of `Fn`-family traits' type parameters is subject to change. Use parenthetical notation (Fn(Foo, Bar) -> Baz) instead (see issue #29625)
I would also like to add a trait which includes the Fn trait (which I don't know how to define, unfortunately). Is that possible as well?
Edit:
Just to clarify: I have been developing in C++ for quite a while, the C++ solution would be to overload the operator()(args). In that case I could use a struct or class like a function. I would like to be able to
Pass both functions and structs to the solver as arguments.
Have an easy way to call the functions. Calling obj.method(args) is more complicated than obj(args) (in C++). But it seems that this behavior is not achievable currently.
The direct answer is to do exactly as the error message says:
Use parenthetical notation instead
That is, instead of Fn<(A, B)>, use Fn(A, B)
The real problem is that you are not allowed to implement the Fn* family of traits yourself in stable Rust.
The real question you are asking is harder to be sure of because you haven't provided a MCVE, so we are reduced to guessing. I'd say you should flip it around the other way; create a new trait, implement it for closures and your type:
trait Solve {
type Output;
fn solve(&mut self) -> Self::Output;
}
impl<F, T> Solve for F
where
F: FnMut() -> T,
{
type Output = T;
fn solve(&mut self) -> Self::Output {
(self)()
}
}
struct Test;
impl Solve for Test {
// interesting things
}
fn main() {}

Type mismatch in trait bound - Why can't rustc infer the type on its own?

I just ran into an issue in some code of mine and managed to trim it down to the following minimal example :
use std::iter::IntoIterator;
use std::marker::PhantomData;
trait Bar<'a> {
fn compile_plz(&self) -> &'a str;
}
struct Foo<'a> {
ph: PhantomData<&'a str>
}
impl<'a> Bar<'a> for Foo<'a> {
fn compile_plz(&self) -> &'a str {
"thx"
}
}
fn do_something_with_bars<'a, I>(it: I) -> Result<(), ()> where I: IntoIterator<Item=&'a Bar<'a>> {
Ok(())
}
fn take_bar<'a, B>(b: &'a B) where B: Bar<'a> {
do_something_with_bars(vec![b]);
}
fn main() {
let f = Foo{ph: PhantomData};
take_bar(&f);
}
This fails with the following error :
23:5: 23:27 error: type mismatch resolving <collections::vec::Vec<&B> as core::iter::IntoIterator>::Item == &Bar<'_>:
expected type parameter,
found trait Bar [E0271]
However, changing vec![b] to vec![b as &Bar] works fine. But since b is of type &B, and B has a Bar<'a> bound, why can't the compiler figure out that b is indeed a &Bar?
It could theoretically attempt to 'fix' the type to &Bar. However, you're hitting the variance problem here - a vector of &B is not a vector of &Bar just because a &B is a &Bar, and neither is the inverse true.
Example: Say we treat a vector of &B as a vector of &Bar. Now we have a value of type &C where &C is a &Bar but not a &B - can we put a &C into our vector? Well, obviously not, because the implementation is a vector of &B. So we can only allow reads, not writes. On the other hand, we can attempt to treat a vector of &Bar as a vector of &B - this works fine as long as we only write &Bs into our vector (because it's allowed to accept &Bs of course) - but since it's still a vector of &Bar, it can contain things that aren't &Bs, so we aren't allowed to read from it.
Hence, a container that allows both read and write at the same time needs to be invariant in its generic argument. You'll always have this problems in languages that have both polymorphism and generics in this fashion.
So, back to the actual question: Since this problem exists, there can't be an automatic escape hatch that would turn your expression that is initially going to be inferred to be vector of &B to the type vector of &Bar. Be explicit about your types, and this won't happen.
Full type inference maybe could help here, but I'm not sure if full type inference is even possible in a language like rust.

Resources