Skip to content

Commit

Permalink
feat: Add some traits to the stdlib (#3796)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

## Summary\*

Adds traits for `Default`, `Add`, `Sub`, `Mul`, `Div`, and `Eq` to the
stdlib.

Note that this does not implement operator overloading so the operator
traits must be called by name. We also have no way currently of
implementing a trait for all integer sizes so I've manually implemented
only some common ones. Let me know if there are other impls I should
add.

## Additional Context



## Documentation\*

Check one:
- [ ] No documentation needed.
- [x] Documentation included in this PR.
- [ ] **[Exceptional Case]** Documentation to be submitted in a separate
PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
jfecher authored Dec 13, 2023
1 parent 0bb44c3 commit 8e11352
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 9 deletions.
140 changes: 140 additions & 0 deletions docs/docs/explanations/standard_library/traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
title: Traits
description: Noir's stdlib provides a few commonly used traits.
keywords: [traits, trait, interface, protocol, default, add, eq]
---

## `std::default`

### `std::default::Default`

```rust
trait Default {
fn default() -> Self;
}
```

Constructs a default value of a type.

Implementations:
```rust
impl Default for Field { .. }

impl Default for i8 { .. }
impl Default for i16 { .. }
impl Default for i32 { .. }
impl Default for i64 { .. }

impl Default for u8 { .. }
impl Default for u16 { .. }
impl Default for u32 { .. }
impl Default for u64 { .. }

impl Default for () { .. }
impl Default for bool { .. }

impl<T, N> Default for [T; N]
where T: Default { .. }

impl<A, B> Default for (A, B)
where A: Default, B: Default { .. }

impl<A, B, C> Default for (A, B, C)
where A: Default, B: Default, C: Default { .. }

impl<A, B, C, D> Default for (A, B, C, D)
where A: Default, B: Default, C: Default, D: Default { .. }

impl<A, B, C, D, E> Default for (A, B, C, D, E)
where A: Default, B: Default, C: Default, D: Default, E: Default { .. }
```

For primitive integer types, the return value of `default` is `0`. Container
types such as arrays are filled with default values of their element type.

## `std::ops`

### `std::ops::Eq`

```rust
trait Eq {
fn eq(self, other: Self) -> bool;
}
```
Returns `true` if `self` is equal to `other`.

Implementations:
```rust
impl Eq for Field { .. }

impl Eq for i8 { .. }
impl Eq for i16 { .. }
impl Eq for i32 { .. }
impl Eq for i64 { .. }

impl Eq for u8 { .. }
impl Eq for u16 { .. }
impl Eq for u32 { .. }
impl Eq for u64 { .. }

impl Eq for () { .. }
impl Eq for bool { .. }

impl<T, N> Eq for [T; N]
where T: Eq { .. }

impl<A, B> Eq for (A, B)
where A: Eq, B: Eq { .. }

impl<A, B, C> Eq for (A, B, C)
where A: Eq, B: Eq, C: Eq { .. }

impl<A, B, C, D> Eq for (A, B, C, D)
where A: Eq, B: Eq, C: Eq, D: Eq { .. }

impl<A, B, C, D, E> Eq for (A, B, C, D, E)
where A: Eq, B: Eq, C: Eq, D: Eq, E: Eq { .. }
```

### `std::ops::Add`, `std::ops::Sub`, `std::ops::Mul`, and `std::ops::Div`

These traits abstract over addition, subtraction, multiplication, and division respectively.
Although Noir does not currently have operator overloading, in the future implementing these
traits for a given type will also allow that type to be used with the corresponding operator
for that trait (`+` for Add, etc) in addition to the normal method names.

```rust
trait Add {
fn add(self, other: Self) -> Self;
}

trait Sub {
fn sub(self, other: Self) -> Self;
}

trait Mul {
fn mul(self, other: Self) -> Self;
}

trait Div {
fn div(self, other: Self) -> Self;
}
```

The implementations block below is given for the `Add` trait, but the same types that implement
`Add` also implement `Sub`, `Mul`, and `Div`.

Implementations:
```rust
impl Add for Field { .. }

impl Add for i8 { .. }
impl Add for i16 { .. }
impl Add for i32 { .. }
impl Add for i64 { .. }

impl Add for u8 { .. }
impl Add for u16 { .. }
impl Add for u32 { .. }
impl Add for u64 { .. }
```
48 changes: 48 additions & 0 deletions noir_stdlib/src/default.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
trait Default {
fn default() -> Self;
}

impl Default for Field { fn default() -> Field { 0 } }

impl Default for u8 { fn default() -> u8 { 0 } }
impl Default for u16 { fn default() -> u16 { 0 } }
impl Default for u32 { fn default() -> u32 { 0 } }
impl Default for u64 { fn default() -> u64 { 0 } }

impl Default for i8 { fn default() -> i8 { 0 } }
impl Default for i16 { fn default() -> i16 { 0 } }
impl Default for i32 { fn default() -> i32 { 0 } }
impl Default for i64 { fn default() -> i64 { 0 } }

impl Default for () { fn default() -> () { () } }
impl Default for bool { fn default() -> bool { false } }

impl<T, N> Default for [T; N] where T: Default {
fn default() -> [T; N] {
[T::default(); N]
}
}

impl<A, B> Default for (A, B) where A: Default, B: Default {
fn default() -> (A, B) {
(A::default(), B::default())
}
}

impl<A, B, C> Default for (A, B, C) where A: Default, B: Default, C: Default {
fn default() -> (A, B, C) {
(A::default(), B::default(), C::default())
}
}

impl<A, B, C, D> Default for (A, B, C, D) where A: Default, B: Default, C: Default, D: Default {
fn default() -> (A, B, C, D) {
(A::default(), B::default(), C::default(), D::default())
}
}

impl<A, B, C, D, E> Default for (A, B, C, D, E) where A: Default, B: Default, C: Default, D: Default, E: Default {
fn default() -> (A, B, C, D, E) {
(A::default(), B::default(), C::default(), D::default(), E::default())
}
}
2 changes: 2 additions & 0 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ mod compat;
mod option;
mod string;
mod test;
mod ops;
mod default;
mod prelude;

// Oracle calls are required to be wrapped in an unconstrained function
Expand Down
117 changes: 117 additions & 0 deletions noir_stdlib/src/ops.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@

trait Add {
fn add(self, other: Self) -> Self;
}

impl Add for Field { fn add(self, other: Field) -> Field { self + other } }

impl Add for u8 { fn add(self, other: u8) -> u8 { self + other } }
impl Add for u16 { fn add(self, other: u16) -> u16 { self + other } }
impl Add for u32 { fn add(self, other: u32) -> u32 { self + other } }
impl Add for u64 { fn add(self, other: u64) -> u64 { self + other } }

impl Add for i8 { fn add(self, other: i8) -> i8 { self + other } }
impl Add for i16 { fn add(self, other: i16) -> i16 { self + other } }
impl Add for i32 { fn add(self, other: i32) -> i32 { self + other } }
impl Add for i64 { fn add(self, other: i64) -> i64 { self + other } }

trait Sub {
fn sub(self, other: Self) -> Self;
}

impl Sub for Field { fn sub(self, other: Field) -> Field { self - other } }

impl Sub for u8 { fn sub(self, other: u8) -> u8 { self - other } }
impl Sub for u16 { fn sub(self, other: u16) -> u16 { self - other } }
impl Sub for u32 { fn sub(self, other: u32) -> u32 { self - other } }
impl Sub for u64 { fn sub(self, other: u64) -> u64 { self - other } }

impl Sub for i8 { fn sub(self, other: i8) -> i8 { self - other } }
impl Sub for i16 { fn sub(self, other: i16) -> i16 { self - other } }
impl Sub for i32 { fn sub(self, other: i32) -> i32 { self - other } }
impl Sub for i64 { fn sub(self, other: i64) -> i64 { self - other } }

trait Mul {
fn mul(self, other: Self) -> Self;
}

impl Mul for Field { fn mul(self, other: Field) -> Field { self * other } }

impl Mul for u8 { fn mul(self, other: u8) -> u8 { self * other } }
impl Mul for u16 { fn mul(self, other: u16) -> u16 { self * other } }
impl Mul for u32 { fn mul(self, other: u32) -> u32 { self * other } }
impl Mul for u64 { fn mul(self, other: u64) -> u64 { self * other } }

impl Mul for i8 { fn mul(self, other: i8) -> i8 { self * other } }
impl Mul for i16 { fn mul(self, other: i16) -> i16 { self * other } }
impl Mul for i32 { fn mul(self, other: i32) -> i32 { self * other } }
impl Mul for i64 { fn mul(self, other: i64) -> i64 { self * other } }

trait Div {
fn div(self, other: Self) -> Self;
}

impl Div for Field { fn div(self, other: Field) -> Field { self / other } }

impl Div for u8 { fn div(self, other: u8) -> u8 { self / other } }
impl Div for u16 { fn div(self, other: u16) -> u16 { self / other } }
impl Div for u32 { fn div(self, other: u32) -> u32 { self / other } }
impl Div for u64 { fn div(self, other: u64) -> u64 { self / other } }

impl Div for i8 { fn div(self, other: i8) -> i8 { self / other } }
impl Div for i16 { fn div(self, other: i16) -> i16 { self / other } }
impl Div for i32 { fn div(self, other: i32) -> i32 { self / other } }
impl Div for i64 { fn div(self, other: i64) -> i64 { self / other } }

trait Eq {
fn eq(self, other: Self) -> bool;
}

impl Eq for Field { fn eq(self, other: Field) -> bool { self == other } }

impl Eq for u8 { fn eq(self, other: u8) -> bool { self == other } }
impl Eq for u16 { fn eq(self, other: u16) -> bool { self == other } }
impl Eq for u32 { fn eq(self, other: u32) -> bool { self == other } }
impl Eq for u64 { fn eq(self, other: u64) -> bool { self == other } }

impl Eq for i8 { fn eq(self, other: i8) -> bool { self == other } }
impl Eq for i16 { fn eq(self, other: i16) -> bool { self == other } }
impl Eq for i32 { fn eq(self, other: i32) -> bool { self == other } }
impl Eq for i64 { fn eq(self, other: i64) -> bool { self == other } }

impl Eq for () { fn eq(_self: Self, _other: ()) -> bool { true } }
impl Eq for bool { fn eq(self, other: bool) -> bool { self == other } }

impl<T, N> Eq for [T; N] where T: Eq {
fn eq(self, other: [T; N]) -> bool {
let mut result = true;
for i in 0 .. self.len() {
result &= self[i].eq(other[i]);
}
result
}
}

impl<A, B> Eq for (A, B) where A: Eq, B: Eq {
fn eq(self, other: (A, B)) -> bool {
self.0.eq(other.0) & self.1.eq(other.1)
}
}

impl<A, B, C> Eq for (A, B, C) where A: Eq, B: Eq, C: Eq {
fn eq(self, other: (A, B, C)) -> bool {
self.0.eq(other.0) & self.1.eq(other.1) & self.2.eq(other.2)
}
}

impl<A, B, C, D> Eq for (A, B, C, D) where A: Eq, B: Eq, C: Eq, D: Eq {
fn eq(self, other: (A, B, C, D)) -> bool {
self.0.eq(other.0) & self.1.eq(other.1) & self.2.eq(other.2) & self.3.eq(other.3)
}
}

impl<A, B, C, D, E> Eq for (A, B, C, D, E) where A: Eq, B: Eq, C: Eq, D: Eq, E: Eq {
fn eq(self, other: (A, B, C, D, E)) -> bool {
self.0.eq(other.0) & self.1.eq(other.1) & self.2.eq(other.2) & self.3.eq(other.3) & self.4.eq(other.4)
}
}
18 changes: 9 additions & 9 deletions test_programs/compile_failure/no_nested_impl/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
fn main() {
let a: [[[[Field; 2]; 2]; 2]; 2] = [[[[1, 2], [3, 4]], [[5, 6], [7, 8]]], [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]];
assert(a.eq(a));
assert(a.my_eq(a));
}

trait Eq {
fn eq(self, other: Self) -> bool;
trait MyEq {
fn my_eq(self, other: Self) -> bool;
}

impl<T> Eq for [T; 2] where T: Eq {
fn eq(self, other: Self) -> bool {
self[0].eq(other[0])
& self[0].eq(other[0])
impl<T> MyEq for [T; 2] where T: MyEq {
fn my_eq(self, other: Self) -> bool {
self[0].my_eq(other[0])
& self[0].my_eq(other[0])
}
}
// Impl for u32 but not Field
impl Eq for u32 {
fn eq(self, other: Self) -> bool {
impl MyEq for u32 {
fn my_eq(self, other: Self) -> bool {
self == other
}
}

0 comments on commit 8e11352

Please sign in to comment.