The following example:
// Sub
use std::ops::Sub;
//
// FloatType
#[derive(Debug)]
struct FloatType<F>(pub F);
//
// &FloatType<F> - &FloatType<F>
impl<F> Sub< &FloatType<F> > for &FloatType<F>
where
for<'a> &'a F : Sub<&'a F, Output=F>,
{
type Output = FloatType<F>;
fn sub(self, rhs : &FloatType<F> ) -> FloatType<F> {
FloatType( (self.0).sub(&rhs.0) )
}
}
//
// difference
fn difference<V>(x : &V, y : &V) -> V
where
for<'a> &'a V : std::ops::Sub<&'a V, Output=V> ,
{ x - y
}
//
// main
fn main() {
let x = FloatType::<f32>( 1f32 );
let y = FloatType::<f32>( 2f32 );
let z = difference(&x, &y);
println!("z = {:?}", z);
}
results in the following compiler output:
... snip ...
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`package`)
note: required for `&'a FloatType<std::collections::HashSet<_, _>>` to implement `for<'a> std::ops::Sub`
--> src/main.rs:9:9
|
9 | impl<F> Sub< &FloatType<F> > for &FloatType<F>
| ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
10 | where
11 | for<'a> &'a F : Sub<&'a F, Output=F>,
| -------- unsatisfied trait bound introduced here
... snip ...
The problem goes away if I change the implementation for the Sub trait to
impl Sub< &FloatType<f32> > for &FloatType<f32>
{
type Output = FloatType<f32>;
fn sub(self, rhs : &FloatType<f32> ) -> FloatType<f32> {
FloatType( (self.0).sub(&rhs.0) )
}
}
Is there a generic way to implemen the Sub trait for this case ?
giocri
February 12, 2026, 4:59pm
2
essentially the issue seems to be that F can be T &T &&T &&&T and so on and your implementations tries to recursively apply to all of them.
i think you need to split the trait for owned types from that for references so that references depend on owned and can have at most one level of referencing
Looks like
opened 09:48PM - 04 Mar 21 UTC
T-compiler
C-bug
fixed-by-next-solver
A-higher-ranked
Hi all,
With @vbarrielle, we're looking at an example of code that fails to c… ompile due to, to the best of our understanding, the type checker infinite-looping.
Minimal code is below:
```rust
use std::ops::Add;
struct Mat<N> {
a: N,
}
// works if we comment this block /*
impl<'a, N: 'a> Add<&'a Mat<N>> for &'a Mat<N>
where for<'r> &'r N: Add<&'r N, Output=N>,
{
type Output = Mat<N>;
fn add(self, rhs: &'a Mat<N>) -> Mat<N> {
unimplemented!()
}
}
// */
fn addition<'a, N: 'a>(lhs: &'a Mat<N>, rhs: &'a Mat<N>) -> Mat<N>
where for<'r> &'r N: Add<&'r N, Output=N>,
{
unimplemented!()
}
fn main() {
addition(&Mat { a: 2 }, &Mat{ a: 3 }); // also works if we comment this line
}
```
[(playground)](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a13ffd4aacd5e61f5c23b7d6c8bb1b9c)
My best guess is that the issue looks like this:
- The type checker tries to verify that the bounds for `addition(&Mat<usize>, &Mat<usize>)` are verified (hence why it builds without the call)
- For this, it must find a N such that `for<'r> &'r N: Add<&'r N, Output=N>`
- Here would be the bug in this hypothesis: maybe for some reason it tries to resolve with `N = Mat<O>` where O would be another free variable
- And then it would infinite-loop with the bound on the `Add` implementation, doing the same on each loop.
The weird thing is, replacing `addition` with `Add::add`, which as far as I could tell should require the exact same bounds, does work. Which makes me guess it's something that is implemented differently for traits and free functions, and traits work well but not when there is a free function like `addition` in the call stack. Maybe an heuristic that ends up primed towards addition of `Mat` by the `addition` function?
Does that make sense, or does anyone maybe have a better idea what this may be due to?
(also, this seems to be related to the references, as removing them does solve the issue — however, removing the universal quantifiers does not appear to help)
Cheers, and as always thank you for all you do! :heart:
Works with the next solver.
In the meanwhile there are a couple workarounds for the OP that don't change the impl.
let x = FloatType::<f32>( 1f32 );
let y = FloatType::<f32>( 2f32 );
- let z = difference(&x, &y);
+ let z = difference::<FloatType<f32>>(&x, &y);
println!("z = {:?}", z);
-fn difference<V>(x : &V, y : &V) -> V
+fn difference<V>(x: V, y: V) -> V::Output
where
- for<'a> &'a V : std::ops::Sub<&'a V, Output=V> ,
+ V: std::ops::Sub,
{
edddd
February 12, 2026, 10:06pm
4
use std::ops::Sub;
#[derive(Debug, Clone)]
struct FloatType<F>(pub F);
impl<F> Sub for FloatType<F>
where
F: Sub<Output = F>,
{
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
FloatType(self.0 - rhs.0)
}
}
fn difference<V: std::ops::Sub<Output = V> + Clone>(x : V, y : V) -> V
{
x-y
}
fn main() {
let x = FloatType::<f32>( 1f32 );
let y = FloatType::<f32>( 2f32 );
let z = difference(x, y);
println!("z = {:?}", z);
}
Thanks for poniting out that F : Sub<Output=F> does not have the problem.
Unfortunately, for my real world application, F may not implement Copy. It may be vector of floats (of any length) and the operations is element wise. It is necessary to have the borrow form of the Sub operator; i.e., the values passed in should not transfer ownership.
edddd
February 13, 2026, 4:19pm
6
use std::ops::Sub;
#[derive(Debug)]
struct FloatType<F>(pub F);
impl<F> Sub for FloatType<F>
where
F: Sub<Output = F>,
{
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
FloatType(self.0 - rhs.0)
}
}
impl<'a, 'b, F> Sub<&'b FloatType<F>> for &'a FloatType<F>
where
for<'x, 'y> &'x F: Sub<&'y F, Output = F>,
{
type Output = FloatType<F>;
fn sub(self, rhs: &'b FloatType<F>) -> Self::Output {
FloatType((&self.0).sub(&rhs.0))
}
}
fn difference<V: std::ops::Sub<Output = U> , U>(x : V, y : V) -> U
{
x-y
}
fn main() {
let x = FloatType::<f32>( 1f32 );
let y = FloatType::<f32>( 2f32 );
let z = difference(&x, &y);
println!("z = {:?}", z);
}
Very interesting. The original code works If one only changes the definition of difference (as edddd suggested above); i.e, the folliowing works:
// Sub
use std::ops::Sub;
//
// FloatType
#[derive(Debug)]
struct FloatType<F>(pub F);
//
// &FloatType<F> - &FloatType<F>
impl<F> Sub< &FloatType<F> > for &FloatType<F>
where
for<'a> &'a F : Sub<&'a F, Output=F>,
{
type Output = FloatType<F>;
fn sub(self, rhs : &FloatType<F> ) -> FloatType<F> {
FloatType( (self.0).sub(&rhs.0) )
}
}
//
// difference
fn difference<V, U>(x : V, y : V) -> U
where
V : Sub<Output = U>,
{ x-y }
//
// main
fn main() {
let x = FloatType::<f32>( 1f32 );
let y = FloatType::<f32>( 2f32 );
let z = difference(&x, &y);
println!("z = {:?}", z);
}
edddd
February 14, 2026, 3:01pm
8
Sorry for bad english, you forgot to put the lifetime of difference so the compiler can't deduce de type correct, it is deducing &FloatType instead FloatType . It try &&FloatType and so on, if you wanna use your original code you can try this
// Sub
use std::ops::Sub;
// FloatType
#[derive(Debug)]
struct FloatType<F>(pub F);
//
// &FloatType<F> - &FloatType<F>
impl<F> Sub< &FloatType<F> > for &FloatType<F>
where
for<'a> &'a F : Sub<&'a F, Output=F>,
{
type Output = FloatType<F>;
fn sub(self, rhs : &FloatType<F> ) -> FloatType<F> {
FloatType( (self.0).sub(&rhs.0) )
}
}
//
// difference
fn difference<'b, V>(x : &'b V, y : &'b V) -> V
where
for<'a> &'a V : std::ops::Sub<&'a V, Output=V> ,
{ x - y
}
//
// main
fn main() {
let x = FloatType::<f32>( 1f32 );
let y = FloatType::<f32>( 2f32 );
let z = difference::< FloatType::<f32>>(&x, &y);
println!("z = {:?}", z);
}
The code I posted above works on my system; see below
package>cat src/main.rs
// Sub
use std::ops::Sub;
//
// FloatType
#[derive(Debug)]
struct FloatType<F>(pub F);
//
// &FloatType<F> - &FloatType<F>
impl<F> Sub< &FloatType<F> > for &FloatType<F>
where
for<'a> &'a F : Sub<&'a F, Output=F>,
{
type Output = FloatType<F>;
fn sub(self, rhs : &FloatType<F> ) -> FloatType<F> {
FloatType( (self.0).sub(&rhs.0) )
}
}
//
// difference
fn difference<V, U>(x : V, y : V) -> U
where
V : Sub<Output = U>,
{ x-y }
//
// main
fn main() {
let x = FloatType::<f32>( 1f32 );
let y = FloatType::<f32>( 2f32 );
let z = difference(&x, &y);
println!("z = {:?}", z);
}
package>cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/package`
z = FloatType(-1.0)
package>cargo --version
cargo 1.92.0 (344c4567c 2025-10-21)
package>
In addition, if I add the function
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
just before main, and add
print_type_of( &z );
In main before the println!, I get that the type for z is package::FloatType<f32>