How to use `using` in a struct for an inheritance-style thing

I don’t know how to write titles :upside_down_face:

Hey! I recently started writing a little mini-project to get a feel for what Odin is like. At this point, it’s not really interactive, but it might be in the future. I’ve stumbled into an issue of how to design the way the structs are laid out.

What I’m doing is I have a struct like Widget, and then structs like Label or Button which do a using widget: Widget in their definitions. I then have a repr proc (short for ‘representation’) which is meant to take in a widget and return a string representation of it.

The issue is that this using call doesn’t work like inheritance as I’m used to. For example, when I have a ^Widget, but the object that contains the widget is actually a ^Button, I can’t use the fields of the button like I’m used to.

I don’t know how to explain my problem. Maybe it would work better if I just showed an example of what I have:

My structs:

Widget :: struct {
  id: string,
  contents: [dynamic]^Widget,
}

Box :: struct {
  using widget: Widget,

  orient: Orient,
}

Button :: struct {
  using widget: Widget,

  text: string,
}

Label :: struct {
  using widget: Widget,

  text: string,
}

Definition of repr procs:

repr :: proc{
  repr_box,
  repr_button,
  repr_label,
}

repr_box :: proc(box: ^Box) -> string {
  arr := make([dynamic]string)
  for widget, _ in box.contents {
    // ISSUE: box.contents is a [dynamic]^Widget, but i want to "upcast" (???)
    // it to its 'real' type so that the repr(widget) call works properly
    append(&arr, strings.concatenate({"  ", repr(widget), "\n"}))
  }

  output := strings.concatenate({"<box>:\n", strings.concatenate(arr[:])})
  return output
}

repr_button :: proc(box: ^Button) -> string {
  return strings.concatenate({"<button \"", box.text, "\">"})
}

repr_label :: proc(box: ^Label) -> string {
  return strings.concatenate({"<label \"", box.text, "\">"})
}

Again, this project is just at a beginning stage, so some things might be very obviously bad design. All I want right now is for the basic repr implementation to work.

Is there a way to make this work as it is? Am I going in the completely wrong direction, and there’s a much more idiomatic Odin solution here? I don’t know. Again, I can’t stress this enough, this is my first project ever in Odin, so there might be some feature that does this exactly as I want that I’m missing entirely.

Thanks in advance!

Just cast it, as long as you have some way of knowing what was the original type. I don’t know if in your case the id field is enough to decide or not… But for instance with typeid you could do it like this:

Widget :: struct {
	type:     typeid,
	contents: [dynamic]^Widget,
}

Button :: struct {
	using widget: Widget,
	orient:       Orient,
}

make_button :: proc(orient: Orient, allocator := context.allocator) -> ^Widget {
	return new_clone(Button {
		widget = Widget {
			type = Button,
			contents = make([dynamic]^Widget, allocator),
		},
		orient = orient,
	})
}

repr :: proc(widget: ^Widget) {
	switch widget.type {
	case Button:
		button := cast(^Button)widget
		repr_button(button)
	case Label:
		// ...
	case:
		fmt.eprintln("unknown widget type")
    }
}

The downside of this is of course that you cannot extend it, because everything is compile type knowledge. So you could go full vtable approach as well. You can take a look at how you can register user formatters in core:fmt which roughtly works the same way… But for prototyping I don’t think you really need this.

Virtual_Table :: struct {
	repr: #type proc(^Widget)
}

custom_type_table: map[typeid]Virtual_Table

repr :: proc(widget: ^Widget) {

	switch widget.type {
	case Button:
		button := cast(^Button)widget
		repr_button(button)
	case Label:
		// ...
	case:
		// user defined custom widgets
		vtable := custom_type_table[widget.type]
		vtable.repr(widget)
    }
}

That does actually work! For some reason I was assuming if I casted the ^Widget it wouldn’t have access to the ^Label or ^Button it came from.

The second approach with vtables seems like an extension of the first approach that can be added later, but it is quite interesting.

The only thing I was wishing was if I wouldn’t have to hard-code a switch into the repr proc with branches for each type within the library (you can see in my original code that I instead chose to use overloading), but they’re really the same semantically so it’s no big deal. I did try to cast the value a little more programatically, but…

$ odin build .
/.../main.odin(135:35) Error: Expected a type, got item.widget_value
        fmt.printfln("%v", cast(item.widget_value)item)
                                ^~~~~~~~~~~~~~~~^

$ odin build .
/.../main.odin(135:30) Error: Cannot call a non-procedure: 'item.widget_value' of type 'typeid'
        fmt.printfln("%v", item.widget_value(item))
                           ^~~~~~~~~~~~~~~~^

Seems like types aren’t exactly first-class in Odin? Oh well, we can’t have everything we want :upside_down_face:

Thanks for the help!

In the errors there, you’re attempting to use a value rather than a type to do the cast. You can try using cast(type_of(item.widget_value))item if you’re set on that approach.

typeid is a runtime value, casting “happens” at compile time. Basically the type of item.widget_value is typeid and not Button or Label, that’s the value of it. Eitherway you will have to have a switch case somewhere to match on the value of item.widget_value.