-
Notifications
You must be signed in to change notification settings - Fork 6
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
A rough idea of a code structure #8
Conversation
After some research, I've realized enum_dispatch could be useful for us. I'm still a bit unsure about the naming, |
The code structure looks ok, but I don't really understand the need for the |
So, we have 3 interfaces (structs): The rust-equivalent code would look something like this: /* cli parsing */
if cli_args.has_flag("serial") {
/* Initialize Serial Interface*/
} else {
/* Initialize OpenOCD Interface*/
} The problem now comes, how do we initialize them? We can't do let interface = if cli_args.has_flag("serial") {
OpenOCDInterface {port: 22} // "port" is just an example struct member. We could have a function here instead, idk
} else {
SerialInterface {port: 22}
};
fn get_interface(args: ...) -> Box<dyn BoardInterface> { /*...*/ }
let interface = get_interface(cli_args);
interface.open(); But this has two downsides:
fn get_data_from_interface(interface: &(impl BoardInterface + ?Sized)) -> u32 { /* ... */ }
// ...
let data = get_data_from_interface(interface.as_ref()); A (cleaner) alternative would be using an enum ( as I have in this PR). pub enum Interface {
Serial(SerialInterface),
OpenOCD(OpenOCDInterface),
JLink(JLinkInterface),
}
fn get_interface(args: ...) -> Interface { /*...*/ }
fn get_data_from_interface(interface: Interface) -> u32 { /* ... */ }
let interface = get_interface(cli_args);
let data = get_data_from_interface(interface); The issues with this comes when we try to interact with the impl BoardInterface for Interface {
fn open(&mut self) -> Result<(), TockloaderError> {
match self {
Interface::Serial(a) => a.open(),
Interface::OpenOCD(b) => b.open(),
Interface::JLink(c) => c.open(),
}
}
} This isn't that bad, it's quite readable code. The issue comes when we have many traits with many defined methods. Personally, I think |
It makes sense. Thanks for the explanation. The only thing that remains on long term is splitting the traits and the implementations into separate folders if needed. |
The more I think about it, the more I agree with this idea. I've never been a fan of "monolith" files, with hundreds and hundreds of lines of code. My initial intention for "BoardInterface" was to be a mega-trait, but that just sounds like a bad idea now. I've let this PR open for a while, and since no new comments were posted I'll go ahead and add today a mockup of how this split-up implementations might look. |
@valexandru Is this (latest commit) what you had in mind? |
I was thinking, maybe we should use anyhow::Error instead of TockloaderError. Or maybe both? After working with TockloaderError, it definitely felt a bit... useless, or extra code for little reason. Most of the error cases would be errors from other crates. |
Okay, after some research I've narrowed down our possibilities of error-handling: 1. Keep going as is with std errorI'm not a fan of this, but it is the most lightweight version. Furthermore, if we do keep the tbf-parser library in here, their error-handling would be similar (as the other options may require the use of std, which we should avoid to maintain potential compatibility with the version on the tock repo.) 2.
|
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.
Some comments, but overall this makes sense to me.
src/interfaces/traits.rs
Outdated
use enum_dispatch::enum_dispatch; | ||
|
||
#[enum_dispatch] | ||
pub trait BoardInterface { |
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.
While the file in tockloader is called board_interface.py (and has been for a long time), in the tockloader.py code I've switched to effectively calling this a "channel". So perhaps renaming this to Channel
or BoardChannel
might resolve some of the headaches with overloading the word "interface".
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.
Yep, I 100% agree with you on this one. I like the name Channel
way better. The name of the Channel
enum and BoardChannel
trait still kind of overlap each other, but that's mostly because I don't know yet how I want to structure the traits, and what behaviour is common between the channels.
src/interfaces/openocd.rs
Outdated
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 don't think each interface/channel needs its own folder, that seems like unnecessary complexity to me. Each interface/channel should be not too complicated, in the hopes that adding new ones isn't too difficult. They should all have the same functionality as well, again suggesting they should be somewhat simple.
That being said, I don't feel strongly about 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.
So you're thinking that each trait should be implemented for all variants (Serial/JLink/OpenOCD) in the same file? Both this, and the current options are equal in my opinion. Though one advantage could be that we could move the declaration to said file (In effect having both the declaration and implementations in the same file). This could prevent trait.rs
from becoming overtly bloated. The traits could optionally be re-exported somewhere?
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.
No, separate files would still be good. Even the folders isn't really a problem. I just think we would like to minimize the amount of per-channel code that is required or expected, because we want to minimize the amount of effort to add a new channel (ie esp tool).
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.
After returning from the holiday break, I'm no sure anymore I completely understand what you were suggestion. So, you are effectively suggesting going from:
📂channels
┣ 📂jlink
┃ ┗ 📜board_interface.rs
┣ 📂openocd
┃ ┗ 📜board_interface.rs
┣ 📂serial
┃ ┗ 📜board_interface.rs
┣ 📜jlink.rs
┣ 📜openocd.rs
┣ 📜serial.rs
┗ 📜traits.rs
to something like this?
📂channels
┣ 📂traits
┃ ┗ 📜board_channel.rs <- contains impl blocks for all channels
┣ 📜jlink.rs
┣ 📜openocd.rs
┣ 📜serial.rs
┗ 📜traits.rs <- still contains the definition of traits
This PR is in responses to #3 and an outline to what was discussed there. It contains no implementation details.
Perhaps, it would be worthwhile to have an additional enum that would contain the different types of interfaces so that we can abstract away what type of interface we actually have.
But then, it would be a bit inconvenient to then call any type of function defined in
BoardInterface
, and the whole concept ofBoardInterface
andInterface
kinda overlap as a concept.As an alternative, we could hide the whole
match
call into an implementation ofBoardInterface
forIntreface
. Possibly, a macro could also be helpful here.We could then again forget about the enum altogether and just use function parameters that implement the
BoardInterface
trait:But this way we need different code paths for all the the interfaces:
Suggestions?