From 8e113526a2d78d27ed4e489f16d5604a2aaa18ea Mon Sep 17 00:00:00 2001 From: jfecher Date: Wed, 13 Dec 2023 13:51:04 -0600 Subject: [PATCH] feat: Add some traits to the stdlib (#3796) # 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. --- .../explanations/standard_library/traits.md | 140 ++++++++++++++++++ noir_stdlib/src/default.nr | 48 ++++++ noir_stdlib/src/lib.nr | 2 + noir_stdlib/src/ops.nr | 117 +++++++++++++++ .../no_nested_impl/src/main.nr | 18 +-- 5 files changed, 316 insertions(+), 9 deletions(-) create mode 100644 docs/docs/explanations/standard_library/traits.md create mode 100644 noir_stdlib/src/default.nr create mode 100644 noir_stdlib/src/ops.nr diff --git a/docs/docs/explanations/standard_library/traits.md b/docs/docs/explanations/standard_library/traits.md new file mode 100644 index 00000000000..63b4f3d6f0b --- /dev/null +++ b/docs/docs/explanations/standard_library/traits.md @@ -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 Default for [T; N] + where T: Default { .. } + +impl Default for (A, B) + where A: Default, B: Default { .. } + +impl Default for (A, B, C) + where A: Default, B: Default, C: Default { .. } + +impl Default for (A, B, C, D) + where A: Default, B: Default, C: Default, D: Default { .. } + +impl 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 Eq for [T; N] + where T: Eq { .. } + +impl Eq for (A, B) + where A: Eq, B: Eq { .. } + +impl Eq for (A, B, C) + where A: Eq, B: Eq, C: Eq { .. } + +impl Eq for (A, B, C, D) + where A: Eq, B: Eq, C: Eq, D: Eq { .. } + +impl 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 { .. } +``` diff --git a/noir_stdlib/src/default.nr b/noir_stdlib/src/default.nr new file mode 100644 index 00000000000..232be74489c --- /dev/null +++ b/noir_stdlib/src/default.nr @@ -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 Default for [T; N] where T: Default { + fn default() -> [T; N] { + [T::default(); N] + } +} + +impl Default for (A, B) where A: Default, B: Default { + fn default() -> (A, B) { + (A::default(), B::default()) + } +} + +impl Default for (A, B, C) where A: Default, B: Default, C: Default { + fn default() -> (A, B, C) { + (A::default(), B::default(), C::default()) + } +} + +impl 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 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()) + } +} diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 3c47de61bab..70b4681b54d 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -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 diff --git a/noir_stdlib/src/ops.nr b/noir_stdlib/src/ops.nr new file mode 100644 index 00000000000..23acc2f0e5d --- /dev/null +++ b/noir_stdlib/src/ops.nr @@ -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 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 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 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 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 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) + } +} diff --git a/test_programs/compile_failure/no_nested_impl/src/main.nr b/test_programs/compile_failure/no_nested_impl/src/main.nr index 916567a7c04..1f1056fb6d9 100644 --- a/test_programs/compile_failure/no_nested_impl/src/main.nr +++ b/test_programs/compile_failure/no_nested_impl/src/main.nr @@ -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 Eq for [T; 2] where T: Eq { - fn eq(self, other: Self) -> bool { - self[0].eq(other[0]) - & self[0].eq(other[0]) +impl 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 } }