trait OutputPin {}
struct Pin1;
impl OutputPin for Pin1 {}
struct Pin2 {
value: i32,
}
impl OutputPin for Pin2 {}
trait Lights {
type Stop: OutputPin;
type Go: OutputPin;
}
struct Crosswalk<T: Lights> {
red: T::Stop,
green: T::Go,
}
fn main() {
let led1 = Pin1;
let led2 = Pin2 { value: 42 };
let traffic_light = Crosswalk {
red: led1,
green: led2,
};
// let value = traffic_light.green.value;
}
In the above code, it is not compiled with a type annotation at the point where traffic_light is assigned.
Is there a way to enable type inference?
If not, how can I make the code compile cleanly?"
To provide a bit more context, I have recently been trying to develop a small project in Rust using embedded_hal. While working on it, I came across a main device that needs to be controlled using several output pins. I tried to manage the pins that control the device by bundling them into a struct, and the code above is a summary of that attempt. At first, I thought that I wouldn't need to specify a separate type because type inference would take care of it. However, it turns out that a type annotation is required when assigning the Crosswalk struct to traffic_light. Is there any way around this? The goal is to bundle several pins for management purposes.
Traits require trait implementations. You're looking for what we could think of as “a struct but at the type level”, but this trait Lights is a “function signature at the type level”, and you need to define the function; the impl Lights for SomeType.
If you provide an implementation and specify it, your code will compile:
struct Pins12;
impl Lights for Pins12 {
type Stop = Pin1;
type Go = Pin2;
}
...
let traffic_light: Crosswalk<Pins12> = Crosswalk {
But that doesn't make it fully inference-based like you want. To do that, you will need to use some struct or tuple that has all of the types as individual parameters. (But you may be able to keep the code cleaner with careful use of type aliases; it's hard to say in this minimal example.)
Rust tries really hard to make sure that unrelated changes to types and trait implementations don't change the results of type inference, or make it fail. In your example a new type could implement Lights with the same associated types and then type inference would no longer be able to decide which type to pick for Crosswalk's T
Thank you for the detailed explanation and guidance. I have modified the code below to make it compile based on what you said. I explicitly specified the types in the Crosswalk declaration as you mentioned, and used trait / associated types to define test_stop(). Although I need to specify the types one by one when declaring, it seems more convenient to use type inference on the usage side. I am not yet familiar enough with Rust to know if there are other parts that need to be fixed... Anyway, thank you so much for your answer!
trait OutputPin {
fn high(&self) {}
}
struct Pin1;
impl OutputPin for Pin1 {}
struct Pin2 {
value: i32,
}
impl OutputPin for Pin2 {}
trait Lights {
type Stop: OutputPin;
type Go: OutputPin;
fn stop(&self) -> Self::Stop;
}
struct Crosswalk<T1: OutputPin, T2: OutputPin> {
red: T1,
green: T2,
}
impl<STOP: OutputPin, GO: OutputPin> Lights for Crosswalk<STOP, GO> {
type Stop = STOP;
type Go = GO;
fn stop(&self) -> Self::Stop {
self.red
}
}
fn test_stop<T: Lights>(traffic_light: T, param_type_test: T::Stop) {
traffic_light.stop().high();
param_type_test.high();
}
fn main() {
let led1 = Pin1;
let led2 = Pin2 { value: 42 };
let traffic_light = Crosswalk {
red: led1,
green: led2,
};
let value = traffic_light.green.value;
test_stop(traffic_light, led1);
}