[Rust] API design and programming practices #279
Replies: 1 comment 1 reply
-
Hey there! Great to have you onboard for the Rust implementation of the stack! Note that the C++ implementation still is far from stable, and therefore a lot of the API is likely to change until we have a stable version. Hence translating the C++ API 1:1 to Rust might still be a tad to early if you ask me. #161 being the biggest core change that will occur probably. Global staticsI agree with you here up to some point. Static globals are convenient to have in some places, but we should try to not abuse them. As the C++ google style guide states (read the details here):
This is also the case for the The same currently holds in other places as well, e.g. the DiagnosticsProtocol and the PGNRequestProtocol and probably more. Where I don't totally agree is by letting the user
A better approach than manually calling Multi-threadingI agree, using CallbacksI don't like the idea of polling for events. If there's no other way, sure. Or if it's really beneficial for performance, I'm okay with it. But there seems to several ways to the observer pattern in Rust: stackoverflow. Note how we also use the observer pattern with weak references in the C++ implementation: event_dispatcher.hpp. Sorry for the wall of text haha, just writing my thoughts down. Love to hear opinions on all this 😄 |
Beta Was this translation helpful? Give feedback.
-
Hello everyone,
While implementing the core concepts of the AgIsoStack in Rust, I came across some problems trying to translate the C++ API 1 to 1.
Of course I expected to find some minor inconveniences as Rust requires us to think more about the data and flow here of.
I don't see a problem with "Rustifing" the API, e.g. changing function names from
get_name()
to justname()
. This will just make Rust programmers more at home without changing a lot about the library.The main problems I have encountered fall into 3 categories,
I would like to discuss with you and express my Rust based opinion on these topics.
Global statics
Based mostly on
can_internal_control_function.cpp
This class contains a list called
internalControlFunctionList
which is a list with pointers to all existing control functions.Now, I get the point of doing it this way in C++ as it gives the
CANNetworkManager
easy access to all the control functions.In Rust however, this will give us problems with the Borrow checker and changing values contained in statics is always considered "Unsafe".
This can be fixed by just not having the static list of references and having the owner of the control function call
update()
manually. Passing in any network manager or data, etc. as needed.(The owner could be; the end user application, a virtual terminal client, task controller client, etc.)
This will however change the public API quite significantly.
Multi-threading
Building multi-threading into a library in Rust, at least in the embedded (
no_std
) world, is not possible, as a lot of embedded targets do not support the standard library.To make the library available to as much platforms as possible, I would separate the library into layers, in the same way as it is done now, but let the end user decide how to process each layer. (One after another, using std::threads, using an embedded schedular like embassy, etc.)
Each layer should thus never block and be completely seperated from the others. Communication between layers is normaly handeled by the user application. Each layer only proccesses the data it is given.
This will again change the public API quite significantly.
Callbacks
Callbacks given to
TestVirtualTerminalClient->add_vt_soft_key_event_listener()
are not easily possible in Rust. The simple reason is that callbacks can not change the global state without access to global statics, which again, is "unsafe".I don't like forcing the end user to write "unsafe" Rust.
I have solved this problem before by having something like
TestVirtualTerminalClient.next_event()
and polling the VT client in the user application.And again this will change the API.
To finish of...
To finish of my rambling, I would like your opinions on the following thought;
"The library should only do work when it is explicitly asked to."
This is a concept most Rust libraries stick to. (as does the core rust team)
I hope we could have a nice discussion about these programming practices.
Beta Was this translation helpful? Give feedback.
All reactions