Cairo impl design #2744
Replies: 4 comments 4 replies
-
Coming from a non-rust background, I initially found traits a little confusing; I thought of it as an interface but it didn't really fit into that paradigm. This put things into perspective a little, great read. impl FooImpl of Foo {
fn bar() {
// ...
}
}
impl AnotherFooImpl of foo {
fn bar() {
// ...
}
} Should both work? |
Beta Was this translation helpful? Give feedback.
-
Thanks for taking the time to write this out. This makes the intent clearer and clears out some of my points in this post. I have some further questions / comments / things that make me wonder if the design could be further clarified. Implementations and modules are kind of the same thing / Suggestion for nameless implIt seems to me like it would be acceptable to do: // modularAddition.cairo
use traits::Add;
impl Add<u8, u8> { ... }
// regularAddition.cairo
use traits::Add
impl Add<u8, u8> { some other impl }
// somewhere_else.cairo
use traits::Add;
use modularAddition;
// or
use regularAddition; If the name needs to be specified, then perhaps just reusing the module name makes sense? This would reduce ambiguity. Confusion about methods not entirely cleared upIt seems slightly awkward to define traits in Cairo 1 as 'a type for a collection of functions' that should be symmetrical, and Your example of trait Something<T, U> {
fn DoSomething() method of T, U {
}
} Or maybe a self-type and traits.On the other hand, generic-less traits might be a mistake. There is already a way to group related functions together: a module. Having traits do that by default just seems redundant. If traits had to have at least one generic type, even an explicit one, it would be somewhat easier to understand their purpose. In practice, literally all the traits in the It would also prevent the issue of shadowing: impl Something of MyTrait {
fn foo(self: A) {}
fn foo(self: B) {}
} This is currently possible but broken (and in fact, should probably bug out since you point out that it would bug out with free functions). |
Beta Was this translation helpful? Give feedback.
-
Regarding nameless implI think it should be possible to just give // moduleA.cairo
impl TryInto<a,b> {...}
impl TryInto<c,b> {...}
impl Into<a,b> {...}
// moduleB.cairo
impl TryInto<a,b> {...}
impl Into<a,b> {...} Let's say I want
The names are unambiguous, there cannot be collisions since you can't define the same implementation for the same types in the same module several times.
in the case where there is ambiguity that needs to be resolved. |
Beta Was this translation helpful? Give feedback.
-
I think this should be: impl Add<u8> for i32 {
type Output = i32;
fn add(self, rhs: u8) -> Self::Output /* or i32 */ {
self + (rhs as i32)
}
} same for other example |
Beta Was this translation helpful? Give feedback.
-
Cairo trait and impl system design
Definition of traits in Cairo.
A trait is a "type" for a collection of functions.
In the same way that a function type is a "type" for a single function.
Let's see the analogy.
Trait:
Here, main expected some
Imp
that has a functionbar
of a known signature, but the functionitself is not known. It is a generic parameter.
Function:
Here, main expects some
f
that is a function of a known signature, but the function itself isunknown. It is a generic parameter.
So, a trait in Cairo is equivalent to in a way to a tuple of functions with some signature. A
generalization of a function type.
To those coming from rust or OOP, this may seem a bit weird. But it is actually a very natural.
There is no Self type in Cairo traits.
There are a few main differences from rust:
Implicit Self generic argument
Rust traits have a hidden generic argument.
For those who know rust or come from OOP background, this may seem really natural and obvious.
This begs the question though, why is there an asymmetry between i32 and u8? Between left and right?
If we wanted to be explicit with Self, we could maybe write it like this:
This is actually what happens in the background.
The implicit Self property has pros and cons:
a.foo()
).This is a tradeoff. In cairo, we chose making Self explicit. We feel this makes the language
simpler and more consistent.
2. Named impls and multiple impls.
In rust, impls are anonymous - they have no name. Also, you can only have one impl per concrete
trait and type.
In cairo, impls behave syntactically and semantically like other items - they are named and you can
define multiple impls (with different names) of the same concrete trait.
The anonymous impl pros and cons:
Impl inference, inconsistent crates and the orphan rules.
Both rust and cairo have impl inference. That means that code can specify a trait, and the compiler
will find the impl to use.
A problem may arise when you have multiple crates that define impls for the same trait and type.
In this case, there is more than one possible impl, and inference will fail.
In rust, there is a unique impl for a trait and type, so you will get a compilation error.
Therefore, it would be impossible to depend on these two crates, which is bad.
Rust introduces the orphan rules to solve this problem.
Impls must be defined near the trait or the type.
In Cairo, there are no orphan rules nor uniqueness demand. To solve this problem when it occurs,
instead, you can always be explicit about which impl you meant. This is why impls are named.
Inference location.
In rust, because of orphan rules it is enough to check for its definition near the trait or the
type.
In cairo, impls are looked up in the current scope, in the trait scope and in the scope of each
of the generic arguments.
Impl names revisited.
Are names on items necessary? They have no semantic meaning. They are there to describe the item
for better readability.
For example, a function name indicates what it does. A variable name indicated what it holds.
In a rust world where impls are uniquely defined by trait and type, the names of the trait and the
type hold all the information, so a name on the impl is redundant.
In a cairo world where multiple impls are allowed, however, maybe there is a meaning for the name?
Here are some examples where names have meaning:
There are cases, however, where the name is redundant. In this case, it is desired to somehow avoid
this name, while still being able to explicitly refer to the impl in case of inference ambiguity.
There are some ways to solve this, but the best way is still an open question.
Suggestions to this are welcome.
Methods
A method is an ergonomic way to call a function on a value:
value.foo()
.In rust, you can define a method by adding taking any trait/impl on a certain type, and using the
self keyword:
In Cairo, you can define a methods by taking any trait.impl and using the self keyword. Note that
unlike rust, there is no restriction on the type of the trait/impl, since traits and impl have
no Self type.
In rust, conceptually, methods are defined on the "type". Though, they can actually be defined in
unrelated files for unreleated traits. Since traits have a Self type, this is a natural place
to put methods.
In Cairo, there is no such natural place. Conceptually, methods could be defined anywhere, even on
free functions, using the self keyword. Having methods only on impls does not have the benefit
of having the associated Self type.
However, having methods on free functions can lead to name conflicts:
This is why in Cairo, methods still can only be defined on impls.
Beta Was this translation helpful? Give feedback.
All reactions