-
Notifications
You must be signed in to change notification settings - Fork 201
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
Controller Area Network (CAN) Take 4 #314
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @therealprof (or someone else) soon. Please see the contribution instructions for more information. |
This looks good to me! I have a couple of thoughts but apologies if they've already been discussed in the previous threads...
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timokroeger If you could resolve the merge conflict, this seems to be ready to merge to us.
We agreed this is ready for merge. Could you rebase/resolve the conflict @timokroeger ? |
Invalid CAN identifiers are still memory safe.
The blocking traits borrowed the naming convention from socketcan. Rename the methods to `transmit()` and `receive()` to make them consistent with the `nb` traits.
e8852cb
to
6b3820e
Compare
Rebased to resolve the changelog conflict. The PR is ready to be merged. |
The MSRV would need to be raised to Rust 1.46.0 in order to keep the |
Just in right time. I would like to use it in https://github.com/esp-rs/esp-idf-hal. |
Is anybody opposing increasing MSRV to 1.46 to get this over finish line? |
Opinions @therealprof, @ryankurte ? |
src/can/id.rs
Outdated
|
||
/// Creates a new `StandardId` without checking if it is inside the valid range. | ||
#[inline] | ||
pub const fn new_unchecked(raw: u16) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does
new_unchecked()
actually need to be unsafe, i.e. might any memory safety rely on it later?
I'd actually argue that yes, it must be an unsafe
function. Implementations and users of the traits should be able to rely on StandardId
/ExtendedId
always containing a valid ID. In my opinion, that's the entire point of using such wrapper types in the first place (ref "Parse, don't validate"). "Relying on" should then mean that even writing unsafe code based on this assumption should be possible - without the need for additional validation of the passed IDs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, for StandardId
it is not marked as unsafe but for ExtendedId
it is:
Lines 62 to 66 in 6b3820e
/// Creates a new `ExtendedId` without checking if it is inside the valid range. | |
#[inline] | |
pub const unsafe fn new_unchecked(raw: u32) -> Self { | |
Self(raw) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unsafe on ExtendedId
was an oversight, pushed a fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO both perspectives on the unsafe
are appropriate here. Are there any "unsafe API guidelines" which could help us find a decision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My reasoning for functions needing to be unsafe
is as follows: As soon as there exists a way to create an instance of StandardId
containing an invalid ID from safe Rust, any possible downstream code which wants to rely on it containing a valid ID must add its own validation checks to ensure that it does. The is necessary for logic purposes and UB-purposes alike.
If, however, the API "contract" of the StandardId
type is to only ever contain a valid ID and from safe Rust only abiding instances can be created, then downstream code can rely on this guarantee. Again, this is useful for logic reasons and UB avoidance alike.
As an example from the Rust stdlib, from_utf8_unchecked()
is also unsafe. The reason is that some code handling str
s downstream might want to optimize on the guarantee that str
always contains valid UTF-8 data. Analogous, a CAN peripheral driver might want to optimize on the assumption that the u16 representing an ID will only ever have some of its lower 13 bits set. By keeping these functions safe, we're disallowing any such downstream code to be sound without additional code for validation.
Asking the other way around: What is gained from making these functions safe in the first place? For statically initializing a variable of type StandardId
, it is irrelevant: The check in ::new()
will be constant-folded away in probably all conceivable cases. The only place where ::new_unchecked()
is really needed is when an ID is dynamically retrieved from elsewhere and you can guarantee that it has already been validated before. And even then, it is only relevant for edge-cases where the last bytes of memory and cycles of CPU time need to be optimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @Rahix. It seems *_unchecked()
APIs are typically marked as unsafe
in rust std lib and also for example in heapless
.
Are those CAN traits going to be in v1.0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am generally against marking functions as unsafe
unless they actually lead to UB, and especially against doing it just because they seem like "scary" functions or some extra care (unrelated to UB) is required. In this case though I think @Rahix is right: the list of Rust UB explicitly includes "Producing an invalid value... The following values are invalid: Invalid values for a type with a custom definition of invalid values".
So, if we do reasonably define this type as only containing valid IDs, then it's right to say producing an invalid valid could cause UB, if some other code later depended on this property in order to be sound. I can't think of any such situations but I guess they could exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we just remove new_unchecked()
for now?
One downside is that using unwrap
and expect
introduces format bloat and makes binaries bigger and in this case there is also a little performance hit by always checking those values.
let canid = read_can();
let id = ExtendedId::new(canid).expect("always valid");
vs
let id = unsafe { ExtendedId::new_unchecked(canid) };
Invalid CAN identifiers are still memory safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, MSRV bump seems fine to me, and real excited to try this...
thanks everyone for the huuge effort in prior PRs and to get this over the line!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timokroeger Could you also do the following?
- Add a couple of unit tests for the functions that have an implementation.
- Raise the MSRV to 1.46.0
Looks good to me as well. 👍🏻 |
A bit late, but what about the timestamp API, which was proposed in earlier PR? I would like to use this for UAVCAN, which needs timestamp support. I guess it can also be added later as a non breaking change to not block this merge with discussions on wether to use |
It would be better if timestamp API is added later. It might take a lot of time to figure out what is the best way for that. |
yep, definitely a few other dependencies so we won't include this here. feel free to open a new issue / PR to discuss this ^_^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, let's get this merged. I will add the couple of unit tests in a separate PR.
Thank you @timokroeger for your work!
Thank you as well to everybody else involved in all the previous PRs and discussions!
bors merge
Should we backport this to 0.2? |
This could be backported if there is interest. We would need to remove the |
I guess it also depends on when |
Sure. But we need several implementations to be sure all work as expected before stabilize in 1.0. I see 0.2 as test playground, not alternative implementaion. Nothing similar to python2/3. |
Updated to the latest HAL changes:
try_
prefixnb
moduleblocking
implementaionsUsage Example
stm32-fwupdate
Implementations
Updated for this PR:
Based on the very similar predecessor traits
embedded-can
v0.3 (diff v0.3 -> this PR)Previous Discussion