-
Notifications
You must be signed in to change notification settings - Fork 101
Discussion: A better Message trait #156
Description
Introduction
Currently the Message trait is defined like this
pub trait Message
where
Self: Sized,
{
fn message_id(&self) -> u32;
fn message_name(&self) -> &'static str;
/// Serialize **Message** into byte slice and return count of bytes written
fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize;
fn parse(
version: MavlinkVersion,
msgid: u32,
payload: &[u8],
) -> Result<Self, error::ParserError>;
fn message_id_from_name(name: &str) -> Result<u32, &'static str>;
fn default_message_from_id(id: u32) -> Result<Self, &'static str>;
fn extra_crc(id: u32) -> u8;
}Then types implementing Message are structs of several *_DATA types that store the actual data for the message. Each of those types has ser and deser methods but no unifying trait to encompass this behaviour. They also have an associated constant ENCODED_LEN and implement Default.
Current limitations and issues
- Returning a
Result<T, &'static str>isn't very helpfull in this case. There is a single reason for this to fail: the massage with the given name/id doesn't exist! However, the type indicates that many different error messages could be emitted. A better type would beResult<T, MessageDoesNotExist>or simplyOption<T>. message_id_from_name,default_message_from_id,extra_crc,message_idandmessage_nameare all trying to get some metadata about the message, though some do that with an instance of the message and others are static. We could instead return a&'static MessageMetatype that stores all the metadata in a single place. This would allow stuff like getting the message name from an id without having to construct the message. We would also avoid having to do multiple calls if we want multiple pieces of metadata. The metadata struct can also be passed around which is pretty usefull.- In order to get a default instance of a message we need to construct it, even if we only need a reference to it. So, this
MessageMetastruct could also store the default value for the message. Then we would have oneMessageMetastatic for every message*_DATA. - Currently there is a mandatory dependency on
heapless, but very little of the library is actually used (onlyVec) and those uses could just be standard Rust arrays. - Instead of implementing
BytesandBytesMut, use thebytescrate. It's a tiny crate that does pretty much the same as this crate's implementation, but uses a trait instead of a type, and implements the trait for slices.
A better interface
pub struct MessageMeta<M: Message> {
pub id: u32,
pub name: &'static str,
pub extra_crc: u8,
pub serialized_len: u8,
pub default: M,
}
pub trait Message: Sized + 'static {
fn meta(&self) -> &'static MessageMeta<Self>;
fn meta_from_id(id: u32) -> Option<&'static MessageMeta<Self>>;
fn meta_from_name(name: &str) -> Option<&'static MessageMeta<Self>>;
fn serialize(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize;
fn deserialize(
version: MavlinkVersion,
msgid: u32,
payload: &[u8],
) -> Result<Self, error::ParserError>;
}Additionaly, every message could implement another trait
pub trait MessageData<M: Message>: Default + Into<M> {
fn meta() -> &'static MessageMeta<M>;
fn serialize_payload(&self, version: MavlinkVersion, payload: &mut [u8]) -> usize;
fn deserialize_payload(version: MavlinkVersion, payload: &[u8]) -> Result<Self, ParserError>;
}this trait is parameterized on M: Message since a single *_DATA type can be used in multiple message enums. Issues 4 and 5 are about the internal message implementation and not the external interface, but switching to use them is pretty trivial. For serde, we can use serde_arrays which is also tiny, at least until serde supports const generics.
Also, using bytes might make it easier to integrate with other libraries in the future, such as tokio_util::codec::Decoder.
Final notes
I have implemented pretty much all of these changes already here, but I would like some feedback if these are things you would be interested in changing in the library. It's a big change, but I think the API would be much better with them. Anyway, let me know what you think!