Skip to content
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

Reconfigure Spi new functions for better type inference #706

Closed
wants to merge 2 commits into from

Conversation

fu5ha
Copy link
Contributor

@fu5ha fu5ha commented Oct 24, 2023

Does what it says on the tin :) unfortunately a breaking change but it means we can get rid of the Spi::<_, _, _, 8>:: paradigm.

@jannic jannic added the breaking change This pull request contains a SemVer breaking change label Oct 24, 2023
@jannic
Copy link
Member

jannic commented Oct 24, 2023

Seems to be a good tradeoff. In many cases, the word size will be 8 bits anyway, so calling the new API gets simpler. And for other word sizes, the change to Spi::new_with_data_size is easy.

///
/// Initialize it with [`.init`][Self::init]
/// or [`.init_slave`][Self::init_slave].
pub fn new_with_data_size<const DS: u8>(device: D, pins: P) -> Spi<Disabled, D, P, DS> {
Copy link
Member

@jannic jannic Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this method should be moved down to the impl<D: SpiDevice, P: ValidSpiPinout<D>, const DS: u8> Spi<Disabled, D, P, DS> block? It's a little bit strange to have the constructor for the bit-generic version in the 8-bit impl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The only awkward thing is that now there is already DS const generic from the impl block in scope, so I had to rename it on the function level parameter. But not that big of a deal.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any update on the branch on github. Did you perhaps forget to push it?

Regarding the function level parameter: I think you don't need it at all, you can use the parameter from the impl block:

impl<D: SpiDevice, P: ValidSpiPinout<D>, const DS: u8> Spi<Disabled, D, P, DS> {
  [...]
  pub fn new_with_data_size(device: D, pins: P) -> Spi<Disabled, D, P, DS> {
    [...]
  }
  [...]
}

Which makes me wonder: Is the new_with_data_size name confusing, if it's often possible to call it without specifying the data size at the call location? It's enough if the compiler can somehow infer the correct value for DS, eg. if you later store the value in some struct where the type is specified.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but I feel like storing this in a wider struct or somewhere it could be inferred would be relatively uncommon? Maybe I'm mistaken though.

@fu5ha fu5ha force-pushed the spi-new-inference branch from 663bf77 to d18bc27 Compare November 1, 2023 17:41
/// or [`.init_slave`][Self::init_slave].
///
/// Valid pin sets are in the form of `(Tx, Sck)` or `(Tx, Rx, Sck)`
///
/// If you pins are dynamically identified (`Pin<DynPinId, _, _>`) they will first need to pass
/// validation using their corresponding [`ValidatedPinXX`](ValidatedPinTx).
pub fn new(device: D, pins: P) -> Spi<Disabled, D, P, DS> {
pub fn new(device: D, pins: P) -> Spi<Disabled, D, P, 8> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that choosing 8bits for the fn new a bit arbitrary.
I understand 8 bits were used as the default for the type before, but I don't think it makes it more correct 😛

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be fair I don't have that much embedded experience, but it seems like 8 bits as word size is extremely common in general and would at least not be particularly surprising

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was my assumption as well, but to be honest my experience with embedded peripherals is also quite limited.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a look at embassy-rp to see how they handle the word width. They don't. It's hard coded to 8 bits. 😄
https://github.com/embassy-rs/embassy/blob/main/embassy-rp/src/spi.rs#L87

@fu5ha
Copy link
Contributor Author

fu5ha commented Nov 16, 2023

Maybe a way to solve both comments @ithinuel and @jannic would be to just make fn new<const DS: usize> so you always have to specify DS on the function (if it can't be inferred), but at least now you wouldn't have to _ the other three type parameters. Thoughts?

@ithinuel
Copy link
Member

ithinuel commented Nov 17, 2023

I need to try a few thing, to make sure I'm not missing some inference subtleties.

EDIT: I made this: ithinuel@90c6c82
I only managed to get rid of the turbofish by introducing a sorta builder type.

Let me know your thoughts.

@jannic
Copy link
Member

jannic commented Nov 18, 2023

EDIT: I made this: ithinuel@90c6c82 I only managed to get rid of the turbofish by introducing a sorta builder type.

Let me know your thoughts.

I'm not convinced.
This is all about discoverability and ease of writing code. The current incantation, let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk));, isn't unreadable. It's just difficult to get right if you have to write it.

This is not really getting better with let spi = hal::spi::SpiBuilder::build::<8>(pac.SPI0, (spi_mosi, spi_miso, spi_sclk));.
In some cases (like in the on target tests) it even gets worse, as previously the parameters were inferred from the struct State definition, so the actual code was just let spi = hal::spi::Spi::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk));

I wonder if we could remove the DS parameter entirely? Sure, we'd lose a little bit of type safety. But not much, I don't think this prevents actual errors. However, removing it would prevent us from easily implementing the DMA traits with the correct word width.

@jannic
Copy link
Member

jannic commented Nov 18, 2023

Another option could be to make DS a conventional generic parameter, with different types for the available bit sizes. This would have the additional advantage that it would become impossible to set invalid bit sizes. The current API would allow to configure a device with a bit size of 0 or more than 16 bits. (That device would be unusable though, as the read and write methods are only defined for valid bit sizes.)

@ithinuel
Copy link
Member

I'm not too attached to that parameter TBH. On a setup with multiple spi peripheral with the rp2040 as one controller on the bus, you may need to change the word size between different peripheral and this generic parameter does not allow for an easy implementation of that usecase.

@fu5ha
Copy link
Contributor Author

fu5ha commented Sep 21, 2024

I will close this for now, it may still be relevant but not so much work to revive or redo if we want it...

@fu5ha fu5ha closed this Sep 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking change This pull request contains a SemVer breaking change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants