Could scoped code blocks fit Odin?

I have been comparing ways of expressing an IMGUI API in Odin and Jai.

Odin’s @(deferred_none=...) already gives me most of the lifetime behaviour I want, and I use it like this:

// I write it this way to make it clear which call the scope belongs to.
{lay.element("toolbar")
    lay.style(toolbar_style)
    lay.text("Toolbar")
}

Those braces are only a surrounding scope. The children do not syntactically belong to the element call. This makes it easier to accidentally attach elements to the wrong parent, and makes components harder to compose.

In Jai, I found this form surprisingly useful:

element("toolbar", #code {
    style(toolbar_style);

    if hovered() {
        style(hovered_style);
    }

    text("Toolbar");
});

It looks like a callback, but it does not create a runtime callback or closure. The code block is inserted at the call site and can use the caller’s locals. The implementation can effectively expand to:

open_element(label);
defer close_element();
#insert body;

This is useful for IMGUI systems because the element already exists while its body executes. It can inspect its own hover, focus, or interaction state and then style itself, while its children remain visually nested under the element call.

I am not suggesting that Odin adopt Jai’s general metaprogramming model, and the syntax above is illustrative only. The capability I am interested in is a procedure like construct accepting a statement block that is expanded once in the caller’s lexical scope, without creating a runtime callback or capture object.

I think the same shape could be useful for other scoped operations, such as locks, or transactions, although UI is the use case I have actually implemented.

Would something in this direction fit Odin’s design, or is there an existing Odin-shaped way to express it?

There is already something that solves this specific problem: @(deferred_*) attributes, meaning you could write:

if lay.element("toolbar") {
    lay.style(toolbar_style)
    lay.text("Toolbar")
}

See the overview for me information: Overview | Odin Programming Language

Thanks. I understand that this works mechanically, and that
@(deferred_*) handles the closing side.

The distinction I am trying to make is semantic rather than about
automatic cleanup.

In:

if lay.element("toolbar") {
    // children
}

if says that the body is conditional on a boolean returned by
element. But there is no condition in the model here. In almost every
case the body must execute. I would be making element return true
purely to borrow the scope belonging to the if.

What I am trying to express is that the block belongs to the element:

element("toolbar") {
    // children
}

So I agree that deferred procedures solve the lifetime part. What they
do not express is the composition relationship itself. That this block
is the element’s body, rather than a conditional body following an
element call.

Note: The syntax is not a proposal, its for illustrattion purposes

This is certainly an interesting idea and I myself who work on UI systems end up with begin/end blocks for exactly this reason. That said, a begin/end encapsulating block is clearer what it is doing and doesn’t hide how its internals get implemented.

The example element("toolbar") { ... } is doing a lot of internal work for minimal gain. An equivalent in IMGUI is if begin_element("toolbar") { ... end_element() }. Because that’s what element("toolbar") { ... } is, ultimately. It’s just a declarative convenience over imperative programming.

The result is that you end up with slightly slower performance over time as the codebase scales unless you do some kind of pre-compile pass.

I don’t know how Jai does it’s meta programming, but I know it has it, while Odin has minimal meta programming if any at all, and those @(deferred_*) attributes are one form, just very lean and minimal. Jai I believe has more meta programming features which I think Odin shys away from by design.

The syntax example I provided was not a proposal (as noted).

Look for example at Jai’s syntax:

element("toolbar", #code {
    style(toolbar_style);

    if hovered() {
        style(hovered_style);
    }

    text("Toolbar");
});

This is much clearer if you are familiar with #code.
You would immediately know that the function captures code to control the order of execution within it.
And I would argue that its much more confusing to use @deferred_*, since you don’t get any clue of its existence when writing the function. You would have to know about its implementation to know how to scope your code.

As for the begin/end pattern, I don’t think it’s clearer.
It might be clear that a call to ui.close() closes an element, but its not clear which element it closes, its hard to jump to the correct closing/opening tag, and easy to forget to close, refactoring becomes harder, and the compiler doesn’t catch misuse.

When you call open and close you are also abstracting what is happening. its not clearer than a scoped block, which tells the reader much clearer where a the beginning and end of a block starts, especially when you have nested elements, and then you also have indentation that is automatically supported by the formatter.

When it comes to performance I’m not sure I understood your point.

I think you’re thinking about something completely different to what I’m thinking. I thought you were looking for declarative convenience, but it sounds like you’re looking for something else? Correct me if I’m wrong.

Also:

if begin_tab("Dashboard") {
    

    end_tab()
}

What part of that is not clear to you? How about just don’t forget, and if you do forget to call something, it will be obvious when debugging. Again, it’s convenience sugar coating an imperative design by nature. Unless you can prove that it’s “better” than begin/end scoping, which every IMGUI framework on the planet uses, I’m not convinced the idea you’re proposing is of great benefit.

And if one uses the deferred attribute, you’d be naming your procs omitting begin_* so no one accidentally thinks to call its equivalent end_* in its enclosing block.

Also, I’m not going to argue about the structure of code. It’s a complete waste of everyone’s time because everyone wants something different. I just accept what I’m given in the language and move on. Code structure is purely subjective and not an argument I want to have.

There is also an ongoing github discussion about this topic and a proposal for a new keyword with in the Odin language. See:

Interesting, looks like that proposal does touch on the same points I made, thank you for sharing!

1 Like