Naming Render traits / methods

In fix(widgets)!: rename `StatefulWidgetRef::render_ref` to `render_stateful_ref` by joshka · Pull Request #1184 · ratatui-org/ratatui · GitHub @kdheepak wrote:

I have a preference for renaming these new trait names (the ones that are unstable) to Render and RenderWithState and then changing the names of the trait methods to match accordingly.

The disadvantage is that it would break from convention with the Widget and StatefulWidget traits. The advantage is that it is more consistent and clear (imo).

And I can see a world where everyone moves to using these new traits, and we deprecate the old ones and remove them eventually (a few years later?).

In general I’m in agreement with the Render/RenderWithState naming, but I have some concerns about taking that track at least in the short term.

My initial concerns about this naming approach are that:

  1. The concept of widgets is a useful one. It enables talking about widgets as concrete things “the paragraph widget” rather than as “Paragraph’s implementation of the Render trait”. The ability to talk about and document things easily seems pretty important to me.
  2. Keeping consistent naming with what we have already makes it easy for users to see the concepts as linked. I.e. the WidgetRef trait is the same as the Widget trait, but takes a ref instead of consuming self. This sort of discoverability for existing users and newer users seems important.
  3. Using the newer names for this puts us on the hook to document these concepts well and talk about how they’re different and why from the existing code. I don’t relish the thought of updating all the documentation for this sort of churn (it would be fairly tedious to review all the places where this sort of thing impacts our docs).
  4. The additional work to design / document / communicate the Render/RenderWithState trait stuff would delay stuff which we could really do with having now. (The impetus of this is wanting to disambiguate the Widget/Ref render method from the StatefulWidget/Ref render methods more easily in code.

That said, the downsides of the Widget trait in general are that it’s really anemic:

  • it only handles rendering
  • it does not handle layout, events, ui state (active/hover), interaction, focus, context, frame count, …

It would be really nice to eventually have widgets be a solid trait to put all this (instead of this stuff being implemented piecemeal as Component traits that seem to pop up in various apps).

Taking note of those points, I lean towards continuing with the WidgetRef / StatefulWidgetRef naming of the traits. I’d suggest that we defer the larger change to some later Ratatui revision where we properly design some of these things from scratch.

I agree with this generally.

The concrete concept we have in Ratatui i.e. the “Widget trait” is “something that can be rendered and is consumed on render”. In a trait based paradigm, if we were thinking about expressing this with no priors, I think it’s quite odd for it to be named Widget. I think of traits as characteristics or features, e.g. Renderable. And a collection of traits to make up what a “Widget” is, and Widget would never be a trait that we used.

In Rust we have a convention of naming the trait and the method the same name, and using Render conveys that actionable feature/characteristic/trait of a “Widget”. The current Widget trait would really be named RenderOnce, akin to FnOnce. This is what I would do if I were writing everything from scratch knowing what I know today.

I personally particularly dislike the Ref in the name because to understand it you have to understand the concept that the Widget trait as it exists in Ratatui today takes self as owned but we want to have a new trait that doesn’t take self as owned, the new trait takes self as a reference instead, and hence the new trait name is WidgetRef.

It’s an implementation detail that is already conveyed in the method signature covered in the naming. It feels analogous to writing:

let name = String::new("John");
let name_ref = &name;

imho, it only makes sense to use Ref in a name if the context of concept without the Ref in the name is also important.

I personally would like for the Widget trait as it exists right now to go away ( think 2 years down the line after we soft deprecate it with docs now + hard deprecate it with a deprecation notice ).

In that vision, do we want to subject future users to WidgetRef? I believe the number of future users 2 years later will be significantly higher than the number of users we have today. Doing the work now and subjecting a smaller number of users today to some inconvenience (doc churn, breaking changes, etc) fairly early in the life of the project for a clean API in the future for a larger number of users in the future is my strong preference.

Now, I can see the case being made for the alternate, i.e. if we think the project has reached saturation of growth. I can also see the case for WidgetRef if we decide that thinking about the Widget trait in Ratatui were essential to Ratatui. I believe it is not important, i.e. implementing a trait that makes the object consumed on render is not really something we should enforce. Having the trait for “consumed on render” be named Widget is confusing imo:

  1. it is quite a bit distant from the conventional meaning of a “Widget” in UI libraries,
  2. we are moving to a paradigm where objects don’t have to be consumed on render

I think that the design you found here is great, and it also happens to be a unique opportunity for us to get away with a naming change and I’d like to take advantage of that. If we don’t do it now we’ll be stuck explaining the legacy of Widget and WidgetRef for a long time.

This imo is a strong argument for the rename too. The rename more clearly conveys that that trait only handles rendering. If we never added layouts, events, interaction etc, it’s my opinion that the project would be better off having the trait named Render.

I won’t have time to reply in detail until Friday, but quickly:

  1. The Widget trait confused me a great deal when starting out. Traits are in the language to support generic programming and dynamic dispatch. In Ratatui found almost no use of Widget in either sense (aside from the one draw method). I still don’t know why the trait is used so heavily, even on things that are never going to be passed to the top level full screen “draw”.
  2. I was so confused by this that I spent an afternoon removing the Widget traits from the crate, just to understand if I was missing something fundamental. Code changed very little, and in my mind became clearer. I basically ignore the trait now and get by just fine.
  3. The new “ref widgets” seem to be aimed at using traits for what they are intended for. E.g. dynamic dispatch, dependency injection (collections of widgets). Since the original Widget trait was a mistake, in my opinion, and the new traits are arguably more correct and more useful, naming them more idiomatically seems good.
  4. There is value in a name for a reusable piece of UI. Maybe this term does not need to be tied to a single trait? E.g. the bubbletea package for go calls them “bubbles” but there is no matching interface.
1 Like

I’ll be honest in that I’ve viewed the Widget trait as both a blessing and not as much a blessing.

I’ve been a big fan of the “manual rendering” model that is the core of ratatui. “Take a frame buffer, render a widget to the frame buffer, repeat till ready for final render, and output to terminal”, and the rest ratatui handles. It is conceptually easy to understand. The biggest flaw in this approach is that you end up with UI code that could sit anywhere. Keybind handling code? Eh, UI code there. Have like four tabs? Introduce complicated structs with locks and borrows.
But this is where I like the widget trait - take a data struct, slap a trait on it, and suddenly there is a convenient way to package functionality into a struct. Data? Already there. Input handling? Put it in the impl. Rendering? It’s in the trait.

The problem I’ve seen with the widget trait, is specifically in the examples - if you don’t know what you are looking at, it’s very confusing. The widget trait is an abstraction that gives the library a very powerful and recommended way to both tie state to renders, while still keeping the rendering separate from other logic and keeping render code clean. In fact, I was doing something similar before the trait was implemented.

The issue is that this later of abstraction can end up hiding the simplicity underneath; new users can end up thinking that you can only use the trait, and their simple first steps suddenly also include learning structs and traits and such.

My argument boils down to, I loved the simplicity of renders before the widget trait, and the widget trait is a super super helpful abstraction for helping large programs write good grug code. But I feel that using only the trait in the examples without explaining it, makes straightforward code hard to follow as you end up jumping all over looking at structs and traits and loops, when it could all be in one (admittedly long) loop block with only local variables.

As for the naming of these traits, I feel it makes sense to say “you add the widget trait to this struct, which contains a render function you call with a frame and buffer”. I feel it would be redundant to say “Call Render::render” but saying “Call Widget::Render” makes a lot of sense to anyone with a little UI experience. I’m not super familiar with the rust-idiomatic naming conventions, but my knee jerk is something like Renderable and RefRenderable.
As for the argument that the Widget trait doesn’t handle anything else, I would argue is meant to be on the user in the first place! To require that a widget has to handle events and layouts and stuff would be self-contradicting in the way the library has worked where we give the tools and leave the implementation to the user

1 Like

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