Leveraging the i32 default to type parameters

I have been thinking again on the defaults affect inference issue, and I see that the compiler can already perform this inferences, with just the help of some annotations. More concretely, the existence of i32 as default integer can be leveraged to set defaults in other types.

Take as example HashMap<K,V,S=RandomState>, where the third parameter S would seem that it would inferred to be RandomState but it does not actually happens. For the sake of the example, we get way crazy and want to set also defaults K=String and V=f32. We can do the following.

trait Inference<T> {
	// A NOP to be introduced everywhere to help inference.
	fn inference_nop(&self,_:T) {}
}
impl Inference<i32> for HashMap<String,f32,RandomState> {}
impl<K,V,S> Inference<u32> for HashMap<K,V,S> {}
fn main() {
	let map = HashMap::default();
	map.inference_nop(0);//<--- magic here!
}

These inference_nops should preferably be hidden and put into everything. I suppose this would currently require a procedural macro. This works because if map is constrained to some type other than the default then there is only the Inference<u32> implementation and 0 is unified to 0u32. If map is not fully constrained then it has also both Inference<u32> and Inference<i32>. Consecuently, the compiler disambiguates 0 into 0i32, which forces to use the Inference<i32> impl and in turn the stated HashMap<String,f32,RandomState> default. I made several examples in this playground.

The previous approach is all-or-nothing inference. If we prefer to have each parameter independently inferred, we may use a different trait for each default. See this playground for examples.

trait Inference1<T>{ fn inference1_nop(&self,_:T) {}}
trait Inference2<T>{ fn inference2_nop(&self,_:T) {}}
trait Inference3<T>{ fn inference3_nop(&self,_:T) {}}

impl<V,S> Inference1<i32> for HashMap<String,V,S> {}
impl<K,V,S> Inference1<u32> for HashMap<K,V,S> {}
impl<K,S> Inference2<i32> for HashMap<K,f32,S> {}
impl<K,V,S> Inference2<u32> for HashMap<K,V,S> {}
impl<K,V> Inference3<i32> for HashMap<K,V,RandomState> {}
impl<K,V,S> Inference3<u32> for HashMap<K,V,S> {}

There remain some issues when some of the parameters go against literal defaults. In the example we have V=f32, but if there is some floating literal like 3.0 it would conflict into using the language default of f64 and the impl default of f32. Thefore, this is an error, which coincides with an old consensus.

The purpose of presenting this idea is not to implement defaults this way, since it is clearly hacky. But to show that we may consider, in most aspects, adding defaults as if we would add a trait implementation. With no need to modify the actual inference engine. With any luck it will show some light into the issue.

(I also have made myself an icon after so many years here. So I am no longer a green 'N'.)

This reminds me of a somewhat similar problem I had. I had a function returning impl Iterator and I needed to store it in Option and access the option in a loop where it's checked first and set later. So inference didn't work. Thankfully I could pass noop value to the function without affecting performance:

static STUPID_HACK: Option<Vec<String>> = None;
#[allow(unused_assignments)]
let mut previous = Some(compute_structure("", &STUPID_HACK));
previous = None;

Though now I think I could've also used some FnOnce trick instead.

There's long been a desire to have smarter inference in a whole bunch of cases, like being able to index arrays by more types without affecting inference, being able to do mixed comparisons without affecting inference, etc.

I don't think there are any easy fixes. But the GitHub - rust-lang/types-team: Home of the "types team", affiliated with the compiler and lang teams. is chartered with improving this kind of thing -- probably starting with finishing the new inference engine -- so if you're interesting in working on this stuff, you might want to join in with them.

You can always put code in if false if you want it to affect inference and not run -- I use that in Pre-pre-RFC: syntactic sugar for `Default::default()` - #7 by scottmcm , for example.

2 Likes