Hacker News new | ask | show | jobs
by iTokio 5 days ago
I almost never use inheritance beside using some kind of interfaces/traits to declare a contract.

However, the only time where I’m missing code/data reuse through inheritance is with GUI. Some mostly flat hierarchies of widgets are really powerful ways to declare and compose UI components with shared behaviors.

In rust, the DX for GUI components is always lacking compared to web, C#. With maybe the exception of Slint which is really not Rust anymore.

Is there a way to have good DX for GUI components in Rust?

3 comments

If you want to get inspired by good component DX, try looking at Bevy, the game engine.

But essentially it comes down to traits, newtypes/enum variants, and macros.

I did have a look at bevy ECS approach and find it very verbose and really foreign, it’s in « not rust anymore» territory. Macros are a dangerous tool in terms of long term maintainability and are hurting compilation times.

But it’s still really fresh, they are conscious of the issues and I hope bevy maintainers came up with an elegant design

Right. Bevy has its own run time allocation and dependency system, and it's <<not rust anymore>>. It bypasses Rust's compile time ownership system using unsafe code. The encapsulation may be safe due to run time checking. It's bothersome that such things seem to be needed.
> It's bothersome that such things seem to be needed.

Yes. Rust is not a general purpose language. It's a systems language. Don't use it for GUIs and games and such until somebody has figured out how to do it properly.

All systems languages before Rust (granted, it's not a very long list) were successfully used for GUIs, games, embedded stuff and much more
You can't figure something out this complex without FAFO. Bevy is one flavor of FAFO. It seems to be working out alright so far.
Rust is great for acyclic data structures, but ... in a GUI you always have closures that point back to the widgets they were created for. That's one reason you should probably not use Rust for GUIs and similar problem areas.
Why would that be a problem in rust?
Imagine this fairly basic situation: you have a button object. When clicked, it triggers a closure (callback). And that closure needs to change the button's text to e.g. "Clicked!".

To do this in Rust, the closure needs a mutable reference to the button. However, the GUI event loop also needs a reference to the button to draw it on the screen. In Rust, you can have many immutable references, __or__ exactly one mutable reference, but never both at the same time. Since the GUI framework holds the button, it won't let your closure mutably borrow it at the same time.

Do you find anything "bad" about this code solving the problem?

    use std::rc::Rc;
    use std::cell::RefCell;
    
    struct Button {
        text: String,
        on_click: Option<Rc<dyn Fn()>>,
    }
    
    impl Button {
        fn new(text: &str) -> Self {
            Button { text: text.to_string(), on_click: None }
        }
        fn draw(&self) { println!("[Button: \"{}\"]", self.text); }
        fn fire_click(button: &Rc<RefCell<Button>>) {
            let cb = button.borrow().on_click.clone();
            if let Some(cb) = cb {
                cb();
            }
        }
    }
    
    fn main() {
        let button = Rc::new(RefCell::new(Button::new("Click me")));
        let cb_handle = Rc::clone(&button);
    
        button.borrow_mut().on_click = Some(Rc::new(move || {
            cb_handle.borrow_mut().text = "Clicked!".to_string();
        }));
    
        button.borrow().draw();
        Button::fire_click(&button);
        button.borrow().draw();
    }
Prints:

  [Button: "Click me"]
  [Button: "Clicked!"]
Why would they need them at the same time? The overall state will contain the button.

Many interactive programs can be GetInput(); ChangeState(); DrawState();

Why wouldn't their lifetimes be isolated?

Also the drawing doesn't need a mutable reference.

Isn't composition (react style) better for GUIs anyways?
That’s a good point.

Usually when people say to prefer composition over inheritance, that does not apply to the inheritance found in GUI, but as an alternative way to share behaviors as a property, « has a » instead of « is a », like has a role instead of being a user, admin..

That do not work well with GUI, where hundreds of of components are reusing common containers, widget behavior, it would be very verbose and painful to always declare common behaviors, data.

I think, React is cheating because it’s based on the web which already provide DOM node components and JavaScript, a GC language where most rust issues do not apply, there is already an underlying composition framework, the problem is that it is not easy to reproduce in rust.

It does on COM, but FOSS tends to disregard learnings from commercial platforms.

See VB 5 and 6 (when VBX got replaced by COM), and anything WinRT, UWP, WinUI.

The work Microsoft has been doing on windows-rs translates quite good into Rust, unfortunately the team doesn't like using designers.