Open
Description
Here's a sample program that uses a static HashMap of structs containing boxed values:
#[macro_use]
extern crate lazy_static;
use std::collections::HashMap;
use std::sync::Mutex;
use std::fmt::Display;
trait Value: Send + Display {
fn box_clone(&self) -> Box<dyn Value>;
}
impl Value for isize {
fn box_clone(&self) -> Box<dyn Value> {
Box::new((*self).clone())
}
}
impl Value for String {
fn box_clone(&self) -> Box<dyn Value> {
Box::new((*self).clone())
}
}
#[derive(Clone)]
struct S {
value: Box<dyn Value>
}
impl Clone for Box<dyn Value> {
fn clone(&self) -> Box<dyn Value> {
self.box_clone()
}
}
lazy_static! {
static ref Registry: Mutex<HashMap<String, S>> = {
Mutex::new(HashMap::new())
};
}
impl Registry {
fn get(&self, key: &str) -> Option<S> {
self.lock().unwrap().get(&String::from(key)).map(|s| s.clone())
}
fn set(&self, key: &str, value: S) -> Option<S> {
self.lock().unwrap().insert(String::from(key), value)
}
}
fn main() {
Registry.set("foo", S { value: Box::new(String::from("hello world")) });
Registry.set("bar", S { value: Box::new(123) });
println!("{}", Registry.get("foo").unwrap().value);
println!("{}", Registry.get("bar").unwrap().value);
}
It works as expected but when I replace redundant impl blocks with a generic one like this:
impl<T: 'static + Send + Clone + Display> Value for T {
fn box_clone(&self) -> Box<dyn Value> {
Box::new((*self).clone())
}
}
it fails with stack overflow in runtime:
thread 'main' has overflowed its stack
fatal runtime error: stack overflow
[1] 48231 abort cargo run
I'm new to Rust and not sure whether it's a bug. Maybe I just do something wrong. However I found the error strange because I don't explicitly do recursion or something else that potentially can lead to stack overflow here. Also it's weird that the compiler didn't find any problem because generics are compile-time concern.
By the way when I replace static variable with a local one it works fine even with the generic impl.
rustc 1.31.1 (b6c32da 2018-12-18), macOS 10.14.2
Metadata
Metadata
Assignees
Labels
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
ghost commentedon Jan 16, 2019
Happens with nightly also: playground
ghost commentedon Jan 16, 2019
So it looks like this line
Box::new((*self).clone())
ends up callingbox_clone
probably because
clone
callsbox_clone
here:I haven't gotten this far in the rustbook, so I'm not sure what's all this Box-ing stuff :D
jonas-schievink commentedon Jan 27, 2019
I don't believe this is a bug, but the lint for unconditional recursion could certainly be improved to warn in this case.
jonas-schievink commentedon Jan 29, 2019
This doesn't seem to be the case
jonas-schievink commentedon Jan 29, 2019
To clarify what's happening here: The generic
Value
impl applies toT = Box<dyn Value>
, since all where clauses exceptT: Clone
are satisfied by supertraits ofValue
, and the explicitimpl Clone for Box<dyn Value>
satisfiesT: Clone
.This means that there's now an
impl Value for Box<dyn Value>
which calls back into theClone
impl ofBox<dyn Value>
, which callsbox_clone()
again, leading to infinite recursion.Without the generic impl, the compiler would autoderef the
Box<dyn Value>
to call the contained trait object'sbox_clone()
method instead.feymartynov commentedon Jan 29, 2019
@jonas-schievink thank you for the clarification.
unconditional_recursion
lint work across function calls #57965steveklabnik commentedon Nov 4, 2020
Triage: no change