Hi to everybody,
I come from a Pascal/C/C++/Fortran background and am slowly learning Rust, . What I find really interesting in Rust is its new approach to programming, which avoids many of the traditional OOP constructs I have used countless times in Pascal and C++.
To better understand Rust's approach, I have found a treasure of exercises to do in trying to translate snippets of the Turbo Vision source code in Rust. (Turbo Vision was a Textual User Interface framework that was bundled with Borland Pascal 7.0.) In the early 90s, Turbo Vision programming was my first true experience with an OOP framework, and I learned a lot from it.
At the moment I am trying to understand what is the most Rustacean way to implement the following type:
TEvent = record
What: Word;
case Word of
evNothing: ();
evMouse: (
Buttons: Byte;
Double: Boolean;
Where: TPoint);
evKeyDown: (
case Integer of
0: (KeyCode: Word);
1: (CharCode: Char;
ScanCode: Byte));
evMessage: (
Command: Word;
case Word of
0: (InfoPtr: Pointer);
1: (InfoLong: Longint);
2: (InfoWord: Word);
3: (InfoInt: Integer);
4: (InfoByte: Byte);
5: (InfoChar: Char));
end;
This is an algebraic type, much like Rust's enums, whose purpose is to encode any information about standard events (e.g., mouse movements, keypresses) as well as custom events (using evMessage
). Being Turbo Vision an event-driven framework, this type is used ubiquitously, and it is therefore quite versatile. Messages of the type evMessage
in particular can be triggered by widgets in response to standard events, and they have custom information attached by means of one of the Info*
fields. As an example, when the user operates on a scroll bar, the scroll bar sends an evMessage
event with a specific integer ID (the constant cmScrollBarChanged
) to its parent view, and a pointer to itself in the InfoPtr
field. InfoPtr
can then be queried to determine the new position and update other widgets, e.g., scroll a text view or update a text label.
In Rust, I have written the following code, which compiles and runs fine:
use std::any::Any;
// This is is the equivalent of Pascal's message IDs like `cmScrollBarClicked`
const MY_CUSTOM_EVENT: i16 = 1;
#[derive(Copy, Clone, Debug)]
struct Point {
x: u8,
y: u8,
}
enum Event {
Nothing,
Mouse(u8, bool, Point),
KeyDown(u16),
CharDown(char, u8),
Message(i16, Box<Any>),
}
fn handle_event(ev: &Event) {
match *ev {
Event::Message(MY_CUSTOM_EVENT, ref content) => {
match content.downcast_ref::<String>() {
Some(s) => println!("Custom message with string \"{}\"", s),
None => println!("Unable to downcast"),
}
}
_ => println!("Unknown event"),
}
}
fn main() {
let ev = Event::Message(MY_CUSTOM_EVENT, Box::new("hello, world!".to_string()));
println!("Handling an event:");
handle_event(&ev);
}
However, I fear this code is too literally equivalent to the Pascal's original to be Rustacean. For instance, like the old Pascal code, my implementation does nothing to prevent that the same numerical value for the message ID be used in different places to encode different kinds of events.
Do you think there would be a more robust and idiomatic way to implement this type?