-
Notifications
You must be signed in to change notification settings - Fork 223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Correct usage of v2 fallible pin traits #135
Comments
oh neat! so, if you’re implementing the traits it’s totally reasonable to specify as a consumer of the trait, you probably want to keep the where bound as general as possible by making your types generic over the error type as well, something like this. the example you’ve provided will only work with OutputPin impls that define |
Woops, no absolutely not. For this first iteration, I want to not care about the error type, but tell the user that something went wrong by returning I attempted using
which is totally understandable because I haven't put any bounds on Are there any other known occurrences of the v2 traits in use in the wild? |
@ryankurte I also noticed that the v2 traits are not included in the prelude, is this on purpose? |
@ryankurte And how would I use the v2 -> v1 conversion routines? 😅 |
the problem is the a patch for my partial refactoring is here if that's useful.
the example i linked is the only public one i have, but afaik it demonstrates everything you need (with the addition of an I2C bound on the error type perhaps).
it seems like it'd create more naming problems to export them and then change it later..? i figured we'd put em in the prelude in place of the v1 ones when we do a minor release and make the breaking change to v2 as default?
something like this? which might be a good addition to the docs... extern crate embedded_hal;
use embedded_hal::digital::{v1, v2, v1_compat::OldOutputPin};
struct NewOutputPinImpl {}
impl v2::OutputPin for NewOutputPinImpl {
type Error = ();
fn set_low(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error>{
Ok(())
}
}
struct OldOutputPinConsumer<T: v1::OutputPin> {
_pin: T,
}
impl <T>OldOutputPinConsumer<T>
where T: v1::OutputPin
{
pub fn new(pin: T) -> OldOutputPinConsumer<T> {
OldOutputPinConsumer{ _pin: pin }
}
}
fn main() {
let pin = NewOutputPinImpl{};
let _consumer: OldOutputPinConsumer<OldOutputPin<_>> = OldOutputPinConsumer::new(pin.into());
} |
I'm on the fence about this. This is causing anything from annoying warnings, to annoying errors (if there's a version mismatch of embedded-hal between a driver and an application, a
Hm... I think we need good stories for all of these cases.
I thought the idea was to implement v2 in the HAL impls and have auto-conversions for the other two. So I switched a HAL impl to implement v2 but now the applications don't compile anymore because the drivers are incompatible...
|
i'm not sure i follow, i can see how the updated deprecation marker is causing annoying warnings (or errors in case of
yeah :-( but this is the best approach all our bikeshedding came up with.
so you can't auto-implement both ways because you put the compiler into loops. v1 impls are silently compatible with v2 consumers, v2 traits (which hide an this asymmetry is noted in the pr and the docs. the v1 -> v2 approach is much nicer: extern crate embedded_hal;
use embedded_hal::digital::{v1, v2};
struct OldOutputPinImpl { }
impl v1::OutputPin for OldOutputPinImpl {
fn set_low(&mut self) { }
fn set_high(&mut self) { }
}
struct NewOutputPinConsumer<T: v2::OutputPin> {
_pin: T,
}
impl <T>NewOutputPinConsumer<T>
where T: v2::OutputPin {
pub fn new(pin: T) -> NewOutputPinConsumer<T> {
NewOutputPinConsumer{ _pin: pin }
}
}
fn main() {
let pin = OldOutputPinImpl{};
let _consumer = NewOutputPinConsumer::new(pin);
} ideally you should just be able to
yeah, i think that'd be an excellent addition. i also opened #136 with the examples posted here added as rust-docs to their respective modules. |
Too bad Is it possible to hide v1 somehow in user code? |
oof, that's particularly annoying, @therealprof any ideas? |
@ryankurte I'm sort of out of ideas. I haven't quite figured out how to deal with this to be honest. As I've mentioned I've tried to convert some crates but stumbled into lots of problems and haven't quite figured out how to work around them for a smooth transition or better yet, friendly co-existence. From what I've experienced I'm very convinced that we should remove the deprecation marker ASAP and re-release a patch version so that old crates continue to just work. |
yeeah, at least we're learning how hard it is now rather than later i guess... i'm not convinced the deprecation maker is our responsibility, afaik we're using it exactly as it's meant to be used and use of That said, given it's causing friction, i would be okay with a patch with it removed. my thinking at the moment is that the smoothest approach is an 0.3.0 release that swaps v2 to default and updates the prelude, then to semver trick it all back into the 0.2.x series, but i don't feel comfortable enough with semver tricks to try it :-/ |
I agree with this. I can think of two reasons to add
Am I missing something? |
For the record, those use cases can be achieved by using |
@eldruin @hannobraun @ryankurte I totally agree with the anti-pattern. However it is |
@ryankurte Maybe it's time to start over again with those traits and go for a v3 which offers the ability to be composable with v1. I don't think we can expect v1 to be gone any time soon so the design goals would be:
|
comments
i realised this morning that the reason i haven't observed this is that i've been exporting
i'm not seeing anything here that's is a failing of the impl, or any approach that would be measurably different to the approach we've taken tbqh. i'm also reasonably confident that most of these issues can be resolved by a minor release. i've also been using these v2 traits developing drivers against the unchanged
why would they need to implement more than one version on a given pin? afaik this is incompatible with the silent coercion we have from v1 -> v2, and is a duplication of effort when we can provide coercion in the hal for drivers and applications (as we have).
this is the case with v1 and v2, using the v2 traits provides silent compatibility with v1 impls.
this is already the case with v1 and v2. coercion from v1 -> v2 is automatic, reversion from v2 -> v1 requires an explicit
On reflection i think the only important guideline here is that HALs and drivers must not implement both, and coercion needs more documentation. issues and resolutions
proposed releasei've put a minor release together (with the an attempt at the semver trick) if you'd like to try patching to either.
|
The problem is that you cannot have both v1 and v2 in scope, if you do then you need to specify the type everywhere you use it:
If you don't have v2 in scope you cannot use the v2 traits at all:
And this is just a HAL impl. It gets far worse if you actually try to use it in an application with drivers requiring v1.
Silent coercion? If the trait had different method names it would be possible to have them both in available and simply use them as needed.
I haven't been able to make it work despite trying multiple times... maybe I'm doing some nonsense but at least I can make a claim that it is absolutely not straight forward.
That's not going to happen in the foreseeable future. Especially drivers are not going adapt quickly. |
yep, you have to have them in scope. fortunately they can be aliased and/or called using the universal method call syntax.
could you share an example of this so i can see if i can help?
you can theoretically drop a v1 impl into anything with a v2 bound without doing anything. i haven't found any situations where this isn't true, but, i write drivers more than hals...
i've added examples to the docs in: #136 that might help
The embedded-hal crate provides mechanisms for conversion between the two implementations, which precludes anyone implementing both the v1 and v2 traits either in a driver or in a hal, but smooths the process by providing helpers for the conversion between the two kinds. We did it this way to relax pressure on hals / drivers / applications to upgrade (there's no issue with HALs staying at v1 almost indefinitely / until we actually remove the old impl), and to minimize the amount of work required by hals/drivers by allowing them to take a single upgrade step / not worry about implementing or maintaining both impls / bounds.
That's not to say you can't use hals or drivers (or applications) that require a mix of both, or even piecewise upgrade a HAL, just, that you must not use both i think we're talking past each other a bit again :-/ i'd be happy to have a skype chat or something to chat about this / see if we can clarify / resolve your issues ^_^ |
Here is an usage example of the You can see the To reduce the number of parameters necessary in the implementation of the impl<DI, SpiE, PinE> W25<DI>
where
DI: ReadWrite<Error = Error<SpiE, PinE>>,
{
/// Get the JEDEC ID
pub fn get_jedec_id(&mut self) -> Result<[u8; 3], DI::Error> {
let mut id = [0; 3];
self.iface.write_read(&[Commands::JEDEC_ID], &mut id)?;
Ok(id)
}
} I wrote a program using it that reads the JEDEC ID from the device and prints it to an OLED display. All running on the STM32F3Discovery board using the usual board/mcu support crates. See the source code here. I only needed to replace the @jamwaffles I can also send you a PR following a similar strategy. |
Thanks @eldruin, this is actually the clearest error handling example of how to use the new v2 traits that I have found. @therealprof and @ryankurte, would it maybe be a good idea to scrap using the prelude altogether while such significant design changes are happening and are foreseen to happen in the future? For newcomers to crates like embedded_hal, explicit name spaces are a godsend because they remove some of the "magic" which makes troubleshooting the other unrelated problems easier. David |
i put together a small driver example / template ryankurte/rust-embedded-driver, do any of y'all have thoughts about this? otherwise i might look at adding it to our org / docs somewhere to make this whole process easier for people. |
Closing as resolved with documentation work in: rust-embedded/book#246 |
I'm migrating one of my crates to use the new v2 traits in this PR. Before, I had a few trait bounds on
OutputPin
. Is it enough to just change these toOutputPin<Error = ()>
?Before, I had:
Which now becomes:
I don't really care what the error type is for these pins so
()
is enough for this crate's purposes I think, but that's another assumption that might be incorrect. Am I on the right track here?The text was updated successfully, but these errors were encountered: