Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Traits #38

Closed
liquidev opened this issue Mar 20, 2022 · 1 comment
Closed

Traits #38

liquidev opened this issue Mar 20, 2022 · 1 comment
Labels
enhancement New feature or request language feature
Milestone

Comments

@liquidev
Copy link
Member

liquidev commented Mar 20, 2022

After debating the whole idea with myself I have decided that standard library-level traits have too many drawbacks, and we need a proper language-level solution.

Defining a trait

A trait is defined using the trait keyword, followed by the name, followed by a list of function heads, terminated with end.
A function head is the function's name, parameters, and calling convention, without the implementation. Only the instance calling convention should be supported for now; static functions can be added in later (probably after the GC and type are implemented).
For instance, the standard library will define an Iterator trait for for loops:

trait Iterator
  func has_next()
  func next()
end
trait := 'trait' identifier function_head* 'end'

Implementing a trait

A trait can be implemented by any arbitrary type, by using the as keyword inside of an impl block. For instance, to implement Iterator on a user-defined Repeat struct:

struct Repeat

impl Repeat
  func times(n) constructor
    @n = n
  end

  as Iterator
    func has_next()
      @n >= 0
    end

    func next()
      i = @n
      @n = @n - 1
      i
    end
  end
end
impl := 'impl' expression impl_item* 'end'
impl_item := func_item | as_item
as_item := 'as' expression func_item* 'end'

Calling into trait methods

For calling methods defined on traits, we can reuse the existing method call syntax. Combined with creating a struct full of static shims that call the actual method to represent the trait, we can use the syntax Iterator.has_next(repeat) to call the method has_next on repeat's implementation of Iterator.

I considered a few other options:

  • repeat.Iterator.next - can be ambiguous if there are multiple traits with the same name implemented by a type
  • repeat.(Iterator).next, repeat.as(Iterator).next, (repeat as Iterator).next - all function the same way; they do a lookup of what next should mangle to given the trait Iterator in order to call the appropriate method on repeat, but that's unnecessary runtime work that can be done during compilation. In order to understand why the extra work is needed, we need to delve deeper into the implementation details…

The implementation

Traits should reuse the existing dtable mechanism, mainly because it's very fast (finding out a method's implementation is just an array lookup). The way this should be implemented is that function signatures get one more bit of mangling information - the ID of the trait a method is attached to (assuming that on creation a trait receives a unique ID).

The reason why the three options I presented will be hard to implement, is that we need to find out the ID of the trait at runtime. But the trait ID is known during compilation, and as such each shim method in the trait knows the ID too, so the method ID can be precomputed at compile time.

This was referenced Mar 27, 2022
@liquidev liquidev added this to the 0.5.0 milestone Jun 7, 2022
@liquidev liquidev added the enhancement New feature or request label Jun 8, 2022
@liquidev
Copy link
Member Author

Fully implemented as of bbbd5ff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request language feature
Projects
Status: Done
Development

No branches or pull requests

1 participant