-
Notifications
You must be signed in to change notification settings - Fork 31
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
finite_field: Define trait FieldElement and add more fields #16
Conversation
8ff7e87
to
3e7bb79
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could do "better" (for some definition of better that I refuse to provide) if we had the const generics feature that just made beta in Rust 1.51, and then FieldElement
could be generic over values of FieldParameters
. However I don't want to require a beta or nightly compiler, and the macro solution here is more than good enough given the small number of FieldParameter
values we support. I filed #18 so we can revisit const generics once they are in stable rustc
. Otherwise I just have some nits about values and references and more impls, but this looks great to me!
+ std::fmt::Display | ||
{ | ||
/// The integer representation of the field element. | ||
type Integer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the use of an associated type!
src/finite_field.rs
Outdated
type Integer; | ||
|
||
/// Modular exponentation, i.e., `self^exp (mod p)`. | ||
fn pow(self, exp: Self) -> Self; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fn pow(self, exp: Self) -> Self; | |
fn pow(&self, exp: Self) -> Self; |
As written, this will move the value self
into the method pow
, making it illegal to reference the value later. Here's a playground illustrating the problem.
This is true because FieldElement
does not require std::marker::Copy
, and so its values have move semantics. However, all implementations of FieldElement
stamped out by make_field!
do implement Copy
(which is reasonable, since they just wrap a u128
, and thus a bitwise copy is pretty cheap and semantically valid), so if you did want to make pow
take a value (for instance, it might spare callers a &
in some contexts), you could just extend FieldElement
to also be a supertrait over Copy
.
My vote would be to take a reference, to avoid unnecessarily restricting FieldElement
implementations with Copy
, and because taking an immutable reference is useful as a shorthand way to communicate to callers that the method won't mutate, destroy or consume the receiver.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch! In fact, this came up in a follow-up PR I'm working on, but there I didn't really think through what the solution ought to be. I ended up following the compiler and make Copy
a supertrait of FieldElement
. Your solution is better.
Done. I applied a similar to change to inv()
below.
src/finite_field.rs
Outdated
|
||
impl Eq for $elem {} | ||
|
||
impl std::ops::Add for $elem { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was a bit surprised to learn that std::ops::Add::add
takes self
as a value, since Add
doesn't require that the receiver implement std::marker::Copy
. I came across this in the std::ops
documentation:
Many of the operators take their operands by value. In non-generic contexts involving built-in types, this is usually not a problem. However, using these operators in generic code, requires some attention if values have to be reused as opposed to letting the operators consume them. One option is to occasionally use clone. Another option is to rely on the types involved providing additional operator implementations for references. For example, for a user-defined type T which is supposed to support addition, it is probably a good idea to have both T and &T implement the traits Add and Add<&T> so that generic code can be written without unnecessary cloning.
WDYT about also providing
impl std::ops::Add for &$elem {
type Output = $elem;
fn add(self, rhs: Self) -> $elem {
$elem($fp.add(self.0, rhs.0))
}
}
?
And same goes for all the other std::ops
trait implementations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I like this idea, but I would prefer re-using the implementation of
Add
for$elem
:
impl Add for &$elem {
type Output = $elem;
fn add(self, rhs: Self) -> $elem {
*self + *rhs
}
}
- What's the analogous pattern for the assignment operators, e.g.,
AddAssign for &$elem
? Does this even make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I agree, yours is better code reuse.
std::ops::AddAssign::add_assign
explicitly takes&mut self
and returns nothing (which makes sense, the point of+=
is to mutate the value) so I don't think it makes sense to replicate the pattern there. I think the extraimpl
s are only necessary/helpful for those traits instd::ops
where the method receiver is a value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, for Add, Sub, Mul, and Div, and Neg.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, with one nit. Could you please write some issues corresponding to the TODO comments?
Actually, wait: I notice now that the build produces some warnings.
Can these be silenced? |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Defines a new trait, FieldElement, that specifies the API currently implemented by Field, as well as ome additional features. Adds a new macro, make_field!(), which wraps a FieldParameters into an implementation of FieldElement. Changes the implementation of the 32-bit field Field so that it is constructed using make_field!(). Adds three additional fields: Field64, Field80, and Field126.
FYI Rust's const generics just landed in stable 🥳 |
NOW @dconnolly tells us!! |
Partially addresses #10. This PR makes the following changes:
Thanks to @tgeoghegan for the tip about super traits.