Naming Render traits / methods

If we could make Widget take &self instead of self I wouldn’t mind not changing the name. But right now there are lots of cases where you can’t just slap impl Widget for Foo; for e.g. when you want to have state that you don’t want to drop (or cannot drop) or you want to mutate the state while rendering and have to do impl StatefulWidget for Foo with a State = FooState.

Do you feel like StatefulWidget also aligns with your thinking? For me it doesn’t, and Ratatui is the only place where I’ve seen “widgets” and “stateful widgets” used like this.

For me, Render / RenderWithState map more cleanly to exactly what Ratatui is doing here, compared to WidgetRef / StatefulWidgetRef. If we were comparing Widget / StatefulWidget to Render / RenderWithState, I might be indifferent.

TLDR, imo

Render / RenderWithState ~= Widget / StatefulWidget >> WidgetRef / StatefulWidgetRef

Ah, I see. My opinion is whatever makes this (&self) version happen because that’s where I see the most value in this trait.

Many +1s to @Nickel’s points from me.

We can’t though. This breaks too much in ways that are often not simple fixes (code has to be made immutable)

Many of those cases slapping impl Widget for &Foo works

impl Widget for &mut Foo often works there too.

This is more common than you’d think. Designtime vs Runtime properties in many UI libs is a sort-of example of this (albeit, often the list of properties available in both is the same or a sub/super-set of each other).

Apologies, this is what I meant as well. If we were starting from scratch, I wouldn’t have minded if Widget was the name of the trait for something that took &self. But given that we can’t change that now because it’d break too much, we have to choose now between WidgetRef and Render for our new trait that takes &self as reference, and my vote for this new trait is Render. This will allow us to deprecate Widget (the current trait that takes self as owned) in a few years or whenever people stop using it.

If there’s never a need for the current Widget trait (i.e. something that takes self as owned), then hypothetically it can be deprecated at some point in the future. And in that hypothetical future, I don’t want to have another trait named WidgetRef and would rather have this new trait be named Render.

Yes, this accurately describes my confusion when first learning Ratatui. I thought there was something important gained by implementing the Widget trait, and that I was missing something fundamental about how Ratatui worked.

I think I disagree that the abstraction is very powerful. As I said earlier in this thread, I don’t think it Widget provides much value today.

Agreed.

FWIW, this is what Ratatui looks like with all the Widget traits removed:

For the most part, and in my opinion, the changes are merely cosmetic, which suggests to me that the traits are not providing much value.

I agree that they don’t add functionality, but they do encourage a better way to structure your programs. The alternative is that you just impl a render_widget on all of your structs - which could be fine - but I like that the library has a “here is the recommended way” approach.

Likewise, they add a concrete name to a concept. “Ratatui has widgets” is easier to explain than “Ratatui has things which can be rendered to the …” :man_shrugging: I don’t even know how to finish that sentence in a way that really portrays this well.

Anything which combines widgets in a form where the specific type of widget is not known until runtime requires a WidgetRef trait. E.g. the stack container in GitHub - joshka/ratatui-widgets: Extra widgets for Ratatui

Anything which accepts a generic parameter at compile time which can be rendered requires that generic parameter to implement a trait in order to generically render that item. E.g. List<T> in tui_widget_list - Rust

The widget/widget ref traits are useful in what they offer in the future, not just what they offer in the immediate sense.

I wonder where we’re all coming from in terms of our programming language background is? I’m curious if there’s something that shapes out perspectives differently on this in some way or another. My main pre-Rust influences are C# and Java, but I’ve worked on software in JavaScript, Ruby, Perl, Python, Go, C, C++, VB/VB/VB.net, and contributed to a smattering of other things in a variety of languages over the years.

To answer your question of programing background, rust is my second (third) language behind python (which I came to strongly dislike because of dynamic types in a large project. Large for python is like 7 files, but large in rust is a lot more to me) and AutoHotKey which got me into programming enough to learn a language. Since learning rust I’ve done C# and sql at work.

I’ve loved the functional style of Rust, and it’s made me want to learn something like Ocaml or Gleam. I’ve never gotten deep into OOP other than using classes like Rust structs - data containers that can have functions.

Yes, the object safe trait (WidgetRef and/or Render) seems quite useful for dyn stuff.

Ah, so there are useful and sophisticated stuff being done with the Widget trait outside the Ratatui crate. Reading tui-widget-list was pretty educational, thanks for the link!

I think I still agree with @kdheepak that having both WidgetRef and Widget is (unnecessarily?) confusing for the functionality provided.

I see this discussion as teasing apart those two traits, because they are pretty different. Maybe?

Are there technical reasons to encourage use the use Widget in favor of WidgetRef? I would imagine things like tui_widget_list would be achievable with just the ref traits, but maybe I’m missing some benefit of the “consume myself to render” pattern that Widget requires.

I believe most in a “don’t be clever” style of programming. That said, I realize that one person’s “clever” is another person’s “obtuse” is another person’s “beauty”.

So, I’m glad you asked that question. I think especially with respect to naming things, a person’s educational and programming background and even their preferred “programming culture” comes in to play.

I’ve been hacking away since high school in the early 90s, professionally since 1996. Too much to list without making everybody bored. I’ve spent time with a lot of languages. Professionally, almost entirely C++, with a side dish of Perl, Ruby and Python scripting.

On a lesser scale (e.g. running through Advent of Code), Pascal, Common Lisp, Smalltalk, Go, Java. I have tried, multiple times, and failed to stick to learning Haskell, OCaml, and Prolog.

My biggest “hole” is that I have almost zero frontend development experience. Ironically, the first 5-7 years professionally I worked on the GUI libs for the earliest smartphones (e.g. ones that shipped only in Japan, which was first to have the kind of network that could support a smartphone). So, I haven’t done much app programming, but I worked on the GUI toolkits. I’ve largely forgotten most details about that stuff, but it was the inheritance heavy OO style, single threaded event loop, ball of inheritance that everybody hates nowadays. :wink:

I am most influenced by my 15 years at Google working on C++ backend stuff. My last 4-5 years there were spent maintaining the core C++ libs used at the company, which involved meticulously improving things in ways that don’t break anything, and trying to help others get their changes into the core without making things worse (which often meant saying “no” to many proposals), and trying to dispense advice about the “right” way to code. https://abseil.io/ and abseil / Tip of the Week #140: Constants: Safe Idioms are the only public thing I can point to from those days.

Rust is the first language I’ve learned with a strong emphasis on type classes, monomorphization, static polymorphism. In C++, doing metaprogramming well, without brittleness and strange edge cases, is almost a dark art, but in Rust it is much more accessible and hard to do wrong. From a hobby-language point of view I’m not yet sure I actually like rust, but I generally find my code has fewer run time bugs, and that chasing compiler errors is more chill than debugging a run time problem. So, Rust is at least “pretty cool” in my book.

That said, I am most used to coding with “interfaces” and dynamic dispatch (think Java interfaces, Go interfaces, C++ abstract classes, C structs with function pointers). In that context, the idea of a Widget trait that consumes itself upon render, and championing that as something that can be quite useful when composing things, seems surprising and kinda wacky. Maybe this is why I’m half-thinking of abandoning Rust for Go, whose answer for any kind of generic problem is usually “make an interface!”

Genuine question: What does Widget offer that would not be possible with this new trait? Why would a user ever need to use Widget in the presence of this new trait?

If there’s no good reason for Widget to exist, then WidgetRef is not a good name (in my opinion) precisely because it goes against this above point from OP.

Do you think most users intuitively understand what “widgets” mean in this context? I personally had to read the source code the first time I came across this library to understand what it meant.

I would prefer our documentation to say:

use trait Render for making your struct renderable

instead of something like:

use trait Widget if you want it to be consumed on render, use impl Widget for &Foo to not consume it, but also implement WidgetRef if you want it to be to be stackable, …


I agree, this is an excellent question!

Most of my professional experience is data analysis, scientific computing, high performance computing and mathematical optimization (and influences from Python, Julia, C). I’ve only dabbled with using web UI frameworks. I like when code is simple and easy to understand. I prefer reading source code to understand how something works the way it does, and reading documentation to understand why something works the way it does.

Backwards compatibility. When someone implements Widget::render(self, ...), they don’t have to care about making the struct immutable (78 results for github search for fn render(mut, of 800+ files that implement widgets). The effort involved changing a mutating implementation to be immutable is non-trivial. We did this in Ratatui for the BarChart.

I think it was probably a mistake (in the ivory tower sense) to not make render accept a ref in the first place, but IMO, that mistake is fixed by making a very similarly named WidgetRef (and perhaps a WidgetMut).

+1000 for this :slight_smile:

Sounds pretty similiar to my experience (though more Microsoft stuff, and I never learnt List/Smalltalk).

Funnily enough I was experimenting with a thing the other day… crates.io: Rust Package Registry

Gotcha - I think that will help me understand where you’re coming from in your views on interfaces / etc.

The trick to understanding what about this makes sense is that Rust doesn’t have optional arguments. Widgets are effectively parameter objects. This is a pretty normal thing to see in C# / Java whenever a functions param list gets large.

To access the 800 odd existing implementations without having to refactor them to ensure immutabiility. There’s no good reason other than that (as evidenced by our fn render(self) { self.render_ref(); } implementations for all the built-ins. These implementations are there for backwards compatibility with the newer WidgetRef trait.

WidgetRef is Widget as it should have been designed. The reason this is the right name is that widgets are already a concept that is in use, it just takes the wrong parameter.

Widget in general is a fairly overloaded term across various UI libs. I would expect to have to read about how it works for any of Widget, Control, Component, etc. I’d say that they were fairly poorly documented and still have a ways to go to get to the point where I’d happily say it’s easy to understand their usage.

In what module would you put these “renderable types”?

I’d generally say something more like:

A widget refers to any type (struct or enum) that implements the WidgetRef and/or WidgetMut trait.

Note: You may see widgets that implement the Widget trait. This trait exists for backwards compatibility reasons (link). New widgets do not need to implement this trait.

I’d expect that the Widget trait will probably live pretty much forever (i.e. be difficult to deprecate in any sort of reasonable time period).

One possible alternative to consider that sort of solves the argument by punting it would be type alias WidgetRef to Render. I don’t really like that solution though as it still puts us in the same position of trying to describe two concepts instead of one. Most things that change the name drastically leave us in that spot.

+1000 :slight_smile:

I feel like I’m in complete agreement with the first part of this, but not in agreement with the second part.

In Rust, the shorter or more idiomatic names are used for things that are more commonly used or for things that the language would like to incentivize. For example, built-in value types are i32, i64 etc instead of Int32, Int64 or Integer32, Integer64. Similarly with keywords, like fn instead of func or function, or mut instead of mutable.

And we want to incentivize use of this new trait. I could see us writing documentation such that only this new trait is mentioned, with no mention of the existing Widget. In that world, naming it WidgetRef is awkward. The name only makes sense when you know what Widget is, which we don’t need anyone to know about and don’t want anyone to use going forward.

I think we are in agreement that if we were starting from scratch we’d have named this new trait Widget. And I’m happy to agree to disagree about the best name for the new trait being WidgetRef :slight_smile: given backward compatibility reasons.

This is the problematic statement. It presupposes that you can forget history. We need to acknowledge that history because the vast majority of widget libraries use the concept of widgets. Search widget tui on crates.io. This tells me that people understand what a widget is pretty well. These are the descriptions:

  • This is a ratatui widget that displays throbber.
  • Widget List for TUI/Ratatui
  • Tree Widget for ratatui
  • Tree Widget for ratatui with table
  • Bar widgets for tui-rs
  • A ratatui form widget for rendering data in editable fields.
  • Logger with smart widget for the ratatui crate
  • An image viewer widget for tui-rs
  • ratatui stateful widget
    etc.

A somewhat compromise (that I still like less than WidgetRef) might be RenderWidget with fn render_widget(&self, ...). But I really realy like the natural progression to WidgetRef over pretty much anything that would leave us having two mismatched non-symmetric names to deal with and explain. This is a don’t make me think type thing. If I know what a Widget is, then I can correctly guess exactly what WidgetRef will do. But I can’t know that without reading what any other named trait should do.

Even with that compromise approach, I’d suggest still going with a RenderWidget which somehow aligns with the existing Widget trait (this would be problematic due to this having render() instead of render_widget() as the function name though) and adding RenderWidgetRef and RenderWidgetMut traits.

What about the following?

trait RenderWidget {
   fn render_widget(&self, ...);
}

trait RenderWidgetMut {
  fn render_widget_mut(&mut self, ...);
}

trait RenderWidgetOnce {
  fn render_widget_once(self, ...);
}

This maps to Fn, FnMut, and FnOnce.


Even though I’d still prefer the following:


trait Render {
   fn render(&self, ...);
}

trait RenderMut {
  fn render_mut(&mut self, ...);
}

trait RenderOnce {
  fn render_once(self, ...);
}

living under ratatui::widgets::*. And, in documentation, we’d describe “widgets” as structs that implement the trait Render / RenderMut / RenderOnce etc.

The advantage of this naming convention is that we can add traits in the future that expand the definition of a “widget”.

trait Focusable {
    fn focus(&mut self);
    fn blur(&mut self);
}

or

trait Clickable {
    fn on_click(&mut self, ...);
}

or

trait Lifecycle {
    fn on_mount(&mut self);
    fn on_unmount(&mut self);
}

Small nit: I dislike WidgetRef because it that is what I’d say when reading &Widget. Just had to say that.

Thinking about new users, they won’t know what Widget does, so they’ll have no reason to ascribe a meaning to WidgetRef.

What I found most confusing about Ratatui was the overload of the term “widget” and the trait of the same name. They are related only loosely.

A Ratatui “widget” is a design pattern. Wrap up some parameters into a value and call a function. That’s it. They are just parameters to a function! They are data!

In fact, I can make useful Ratatui widgets without implementing the Widget trait at all. There is only one place, across all of Ratatui itself, that uses the Widget.

In terms of “don’t make me think”, the Widget traits violates that, for me, in spades. The term brings all sorts of connotations from other UI systems, where they are usually much more than blobs of structured passed to a function. It took me quite a long time to figure out that Ratatui widgets were not that.

I think one area Ratatui could improve is to de-emphasize the idea that “Widget makes a widget” and emphasize and explain more prominently the intended “widget” design pattern(s), of which implementing a “render” trait is only one small part. There are plenty of other issues, such as life cycle, when to use stateful widgets, whether to accept &mut self when rendering (and the implications thereof), how to compose widgets, etc.

I hint above, but I think the “widget” concept can survive just fine without a trait named the same, and in Ratatui’s case I think this might even be more clear (more in next section).

I personally find that, when one thing is replacing another, it is easier to understand if they are named differently. I would find it easy to remember that “Widget” was deprecated (because the naming was too broad and mismatched the method name, which is not idiomatic for Rust), and replaced with “Render” traits (which do accurately name the functionality and match the method name, as is idiomatic).

In a future world where Widget, WidgetRef and WidgetMut exist, but Widget is forever holding the position of simultaneously being the most conveniently written and spoken word, but is essentially deprecated, that is the situation that “makes me think.”

As far as history is concerned, I’ve been party to many 5 and 10 year deprecation efforts. Frankly, if the intent is to admit that Widget should simply go away, Ratatui’s user base is small enough that it can happen in that time frame. E.g. mark it deprecated now. In 5 years, rename it LegacyWidget (allowing crates to upgrade with a simple name change). Maybe leave it that way for 5 more years before removing it. When it comes to deprecating things, just be patient and appreciate the power of time. :wink:

Clearly, this is predicated on a deprecation of Widget in the “no longer recommended” sense.

I think Ratatui uses the term “widget” in a far looser sense than just implementing one trait. It is a design pattern, and an expressed intent to allow for re-use and composition of some sort. I think Ratatui might benefit from the absence of a Widget trait. This would emphasize that the concept is more about convention than satisfying any single, narrow, API contract. For example, every non-trivial widget has a whole build-side API as well.

I find it quite easy to imagine a future where a Ratatui “widget” could choose to implement additional traits, at which time it will be strange that one of the traits in the “widget family of traits” is called “Widget” and the others follow a different convention. E.g. the Clickable, Focusable, Lifecycle traits @kdheepak suggests.

Perhaps this is the “curse” of term widget. It is broad and vague enough to stay relevant as additional features are added, even if Ratatui evolves a full fledges stateful UI layer, or adds a reactive state management layer, etc. The concept of a “widget” could likely survive that transition, but the current Widget and envisioned WidgetRef and WidgetMut traits would be increasingly unclear in name.

1 Like

I completely agree with this. It is very useful to understand and use in design, but the use of the Widget trait makes it feel like you are required to implement the widget trait, or you aren’t able to render at all - which is actually quite opposite to how the rendering works.

I personally have been brought over to the side of Render just because when you see the render trait, you say “oh, that’s where the rendering happens”, instead of “that’s where the magic functionality of the widget happens”. Because the rendering in actually very straightforward.

The trait is there to provide a universal contract for calling renderable things in a backwards compatible way, and Widget isn’t very forwards compatible.

1 Like

So I implemented a Focus trait crate:

There I just added a new trait that is both Widget + Focus:

trait FocusableWidget: WidgetRef + Focus {
    fn boxed(self) -> Box<dyn FocusableWidget>
    where
        Self: 'static + Sized,
    {
        Box::new(self)
    }
}

And defined a form which uses it:

#[derive(Focus, FocusContainer)]
pub(crate) struct Form {
    pub(crate) children: Vec<Box<dyn FocusableWidget>>,
}

Fill it with some widgets:

    let mut form = Form::from_iter([
        Label::new("Label 1:").boxed(),
        TextBox::new("Text 1").boxed(),
        Label::new("Label 2:").boxed(),
        TextBox::new("Text 2").boxed(),
        Label::new("Label 3:").boxed(),
        TextBox::new("Text 3").boxed(),
    ]);
    form.focus_first();

Which is then rendered:

impl WidgetRef for Form {
    fn render_ref(&self, area: Rect, buf: &mut Buffer) {
        let areas = Layout::vertical(vec![Constraint::Length(1); self.children.len()]).split(area);
        for (child, area) in zip(self.children.iter(), areas.iter()) {
            child.render_ref(*area, buf);
        }
    }
}

image

The ability to just slap form fields into a vec like this and be able to produce a rendered form is what I’m talking about when I suggest that WidgetRef is a useful abstaction. This wouldn’t be possible to build with the no Widget traits approach.

1 Like

I think we can all agree that having a WidgetRef-like trait would be a good thing (as long as it isn’t required), but we are trying to decide on what the naming convention for it should be. If we were to change the name, it would take 5+ years to depreciate the old name, which means we can’t use the Wdget trait in a way that makes more current sense.

I think the rest of this discussion has been on whether Widgets are required to use the crate (they aren’t) and their existence confusing new users. (Personally I would like to see several examples without the Widget trait explaining the use cases for the Widget trait)

Correct me if I’m wrong though.

( I just wanted to say to everyone thanks for the engaging discussion and thanks for taking the time to articulate your thoughts and explore these ideas in so much detail! )

1 Like