Rust code 0307

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 and G1 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, and ark_std are external crates specified in the [dependencies] section of your Cargo.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 an std::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 an Option containing the index of the first element that satisfies the predicate. If no element satisfies the predicate, it returns None.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *