Table of Contents
Crate page, what to look for
Start with the overview to understand the crate’s purpose.
Explore modules and key types to see what functionalities are provided.
Look at traits and their implementations to understand the crate’s design.
Examine important methods for practical usage.
Find examples to see how the crate is used in practice.
Read detailed documentation for in-depth understanding of specific functionalities.
Different use
Statements
This record comes from that I notice in one file the crates are specified differently
use crate::
use crate::{Fr, G1};
- This means that
Fr
andG1
are defined within the current crate, not in an external crate.
use <crate_name>::
use ark_ec::VariableBaseMSM;
use ark_ff::{PrimeField, UniformRand};
use ark_poly::{univariate::DensePolynomial, Polynomial};
use ark_std::rand::CryptoRng;
- These lines bring items into scope from external crates.
ark_ec
,ark_ff
,ark_poly
, andark_std
are external crates specified in the[dependencies]
section of yourCargo.toml
file.
then in the lib.rs file, I found Fr and G1 as
explicit type parameter specification
Function Definition with Generics
The function generate_blinding_scalars
is defined with a generic parameter R
:
/// generate random scalars, for blind randomness
pub fn generate_blinding_scalars<R: Rng + CryptoRng>(k: usize, rng: &mut R) -> Vec<Fr> {
(0..k).map(|_| Fr::rand(rng)).collect()
}
R
: This is a generic type parameter constrained by the traits Rng
and CryptoRng
. This means that any type R
used with this function must implement both the Rng
and CryptoRng
traits.
Function Call with Explicit Type Parameters
When calling a generic function, Rust often infers the type parameters automatically based on the arguments provided. However, there are cases where you might need to specify the type explicitly. This is done using the ::<Type>
syntax:
let b = generate_blinding_scalars::<R>(40, rng);
Closures
In Rust, closures are anonymous functions that you can save in a variable or pass as arguments to other functions. They are similar to lambdas in other programming languages. Closures can capture variables from the scope in which they are defined. Here’s a detailed explanation of the closure syntax in Rust:
Basic Syntax
The basic syntax for defining a closure is as follows:
let closure_name = |parameter1, parameter2| -> ReturnType {
// closure body
};
Here’s a breakdown of the components:
closure_name
: The variable that will hold the closure.|parameter1, parameter2|
: A pipe-separated list of parameters.-> ReturnType
: The optional return type of the closure.{ // closure body }
: The body of the closure.
example
let add = |x, y| x + y;
let result = add(2, 3);
println!("Result: {}", result); // Output: Result: 5
so closure are like functions, they can have name, inputs, return
enumerate()
The enumerate()
method is an iterator adaptor that transforms an iterator into a new iterator that yields pairs. Each pair consists of the index of the element and a reference to the element itself. The index starts from zero and increments by one for each subsequent element.
it adds an index
zip()
The zip()
method in Rust is a powerful iterator adaptor that combines two iterators into a single iterator of pairs (tuples). Each pair consists of one element from each of the original iterators.
code example
let f_opc = witness["ci"]
.iter()
.zip(witness["q_fjmp"].iter())
.zip(witness["q_bjmp"].iter())
.zip(witness["q_add"].iter())
.zip(witness["q_sub"].iter())
.zip(witness["q_left"].iter())
.zip(witness["q_right"].iter())
.zip(witness["q_in"].iter())
.zip(witness["q_out"].iter())
.map(
|(
(((((((&ci, &q_fjmp), &q_bjmp), &q_add), &q_sub), &q_left), &q_right), &q_in),
&q_out,
)| {
ci + zeta[1] * q_fjmp
+ zeta[1].pow([2 as u64]) * q_bjmp
+ zeta[1].pow([3 as u64]) * q_add
+ zeta[1].pow([4 as u64]) * q_sub
+ zeta[1].pow([5 as u64]) * q_left
+ zeta[1].pow([6 as u64]) * q_right
+ zeta[1].pow([7 as u64]) * q_in
+ zeta[1].pow([8 as u64]) * q_out
},
)
.collect::<Vec<_>>();
note: so many nested parentheses, it is necessary.
if you don’t use the parentheses to correctly unpack the nested tuples, it will result in a compilation error. This is because the nested structure created by multiple zip
calls needs to be properly destructured to access the individual elements.
sort_by method
The sort_by
method in Rust is a powerful and flexible way to sort elements in a collection, such as a vector, based on a custom comparison function. This method allows you to define exactly how two elements should be compared to determine their order in the sorted collection.
Purpose: The sort_by
method sorts the elements of a collection in place, using a custom comparison function to determine the order of elements.
Usage: It is commonly used when you need to sort elements based on a specific criterion or set of criteria that cannot be achieved with the default ordering.
fn sort_by<F>(&mut self, compare: F)
where
F: FnMut(&T, &T) -> std::cmp::Ordering,
Parameters
&mut self
: The method takes a mutable reference to the collection it is sorting. This means the original collection is modified directly.compare: F
: A closure or function that takes two references to elements of the collection and returns anstd::cmp::Ordering
value. The closure defines how two elements should be compared.
fn main() {
let mut vec = vec![(1, 3), (4, 2), (3, 5), (2, 4)];
// Sort the vector by the second element of each tuple
vec.sort_by(|a, b| a.1.cmp(&b.1));
println!("{:?}", vec);
}
The .position()
Method
The .position()
method in Rust is used to find the index of the first element in an iterator that satisfies a given predicate.
fn position<P>(&mut self, predicate: P) -> Option<usize>
where
P: FnMut(&Self::Item) -> bool;
self
: The iterator over which position
is called.
predicate
: A closure that takes a reference to an element and returns true
if the element satisfies the condition, and false
otherwise.
Return Value
Option<usize>
: The method returns anOption
containing the index of the first element that satisfies the predicate. If no element satisfies the predicate, it returnsNone
.
Test in Rust
running cargo test
in the root directory of a workspace will also run tests in all the submodules (sub-crates) that are part of the workspace. This is because cargo test
is designed to run tests for the entire workspace by default.
example structure
my_project/
├── Cargo.toml # Root workspace file
├── pipeline/
│ ├── Cargo.toml # Sub-crate
│ └── src/
│ └── lib.rs
└── other_crate/
├── Cargo.toml # Another sub-crate
└── src/
└── lib.rs
Root Cargo.toml
Make sure your root Cargo.toml
includes the submodules in the workspace:
[workspace]
members = [
"pipeline",
"other_crate"
]
Running Tests
Simply run:
cargo test
This will run all tests in the root crate as well as in all member crates listed in the workspace.members
section. Each sub-crate’s tests will be executed as part of this command.