> So a trait can define abstract functions, but can't access any underlying fields.
In Rust, a trait is a purely abstract interface to a type. It explains how you can use that type, but it knows nothing about the implementation.
I'm not quite sure what the design goal was with the Widget type, but the closest solution is to replace the member variable in the abstract type with some kind of accessor (and to replace the magic negative font sizes with an Option):
trait Widget {
// ADDED: Access whatever underlying font
// size you have stored in this type.
fn desired_font_size(&self) -> Option<i32>;
// ADDED: Access the theme of this object.
fn theme(&self) -> &Theme;
// High-level interface to get font size.
// (With a default implementation in terms of
// other functions on this trait.)
fn font_size(&self) -> i32 {
if let Some(sz) = self.desired_font_size() {
sz
} else {
self.theme().get_standard_font_size()
}
}
}
I'm not sure this is how I'd personally design this, but it should compile.> What IS the idiomatic Rust way to do a cyclical directed graph?
Unfortunately, the correct idiomatic way is that you try very hard to avoid doing so. Rust is all about clear ownership, and it doesn't like circular references.
The usual solution is to put the cyclic graph code into a library, and to use pointers and about 20 lines of unsafe code. (EDIT: See below for a better solution.) It's not much different from the C++ solution.
The Servo team really wants GCed, safe pointer support in Rust, and design work is well underway. But it's going to take a while to stabilize.
GUI libraries are an interesting special case: They involve huge class hierarchies, lots of implementation inheritance, circular references, and tricky ownership. Most older OO languages were optimized for this case. Newer languages tend to favor flatter type hierarchies and far less implementation inheritance. Which means that traditional object-oriented GUI designs may be awkward.