Rust notes

crate and trait

Crate
  1. Definition: A crate is the basic unit of compilation in Rust. It can be a library or an executable.
  2. Purpose: Crates are used to modularize code and can be used to distribute libraries or applications.
  3. Usage: Crates are defined using a Cargo.toml file, and the code is typically organized into a src directory.
  4. Types: There are two types of crates:
    • Binary Crate: Generates an executable. Defined with a main.rs file.
    • Library Crate: Produces a library that can be used by other crates. Defined with a lib.rs file.

Structure of a Crate

my_crate/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── transcript.rs
    └── another_module.rs

In this example:

  • Cargo.toml defines the crate’s configuration.
  • src/lib.rs is the root of the library crate.
  • src/transcript.rs and src/another_module.rs are additional modules.

cope of pub(crate)

When you use pub(crate), the item (e.g., a trait, function, or struct) is accessible across all the modules within your crate. This means any source file within the src directory can access items marked with pub(crate).

Trait
  1. Definition: A trait is a collection of methods defined for an unknown type: Self. They can be implemented for any data type.
  2. Purpose: Traits are used to define shared behavior in an abstract way. They allow for polymorphism and code reuse.
  3. Usage: Traits are declared using the trait keyword and are implemented for types using the impl keyword.

Defining a Trait:

pub trait Greet {
    fn greet(&self);
}

Implementing a Trait:

struct Person {
    name: String,
}

impl Greet for Person {
    fn greet(&self) {
        println!("Hello, my name is {}", self.name);
    }
}
Summary
  • Crate: A package of Rust code that can be compiled into a library or executable. It helps in organizing and distributing code.
  • Trait: A way to define shared behavior across different types, providing a form of polymorphism.

you can define and implement a new trait for a struct from another crate in Rust. This is one of the powerful features of Rust’s trait system, allowing you to extend the functionality of types defined elsewhere.

lifetime

A lifetime is a construct the Rust compiler uses to track how long references are valid. The compiler uses this information to ensure that references do not outlive the data they point to, thereby preventing dangling references and other related issues.

Different Lifetimes in Rust

1. 'static Lifetime

  • Definition: The 'static lifetime is a special lifetime that indicates that the reference is valid for the entire duration of the program.
  • Use Case: It is commonly used for string literals and other data that is known at compile time to be valid for the program’s entire execution.
let s: &'static str = "Hello, world!";

2. Implicit Lifetimes

  • Definition: When lifetimes are not explicitly annotated, the Rust compiler uses implicit rules to infer them. This makes code more ergonomic but can sometimes be less clear.
  • Example: The compiler can infer that the lifetime of y in the following function is the same as the input reference x.
fn implicit_lifetime(x: &i32) -> &i32 {
x
}

3. Explicit Lifetimes

  • Definition: Explicit lifetimes are specified by the programmer to provide more control over how long references are valid.
  • Use Case: They are necessary when the compiler cannot infer lifetimes or when complex relationships between lifetimes need to be described.
fn explicit_lifetime<'a>(x: &'a i32) -> &'a i32 {
x
}

Lifetime Elision Rules

Rust has some rules for lifetime elision, which allows the compiler to infer lifetimes in certain cases. These rules apply to function signatures:

  1. Rule 1: Each reference parameter gets its own lifetime.
  2. Rule 2: If there is exactly one input lifetime, that lifetime is assigned to all output lifetimes.
  3. Rule 3: If there are multiple input lifetimes, but one of them is &self or &mut self (for methods), the lifetime of self is assigned to all output lifetimes.
fn elided_lifetimes(x: &i32, y: &i32) -> &i32 {
x
}

In this example, Rust uses the elision rules to infer the lifetimes.

Complex Lifetimes

For more complex cases, such as those involving multiple lifetimes or lifetimes in structs, explicit annotations are necessary.

struct Example<'a> {
reference: &'a i32,
}

impl<'a> Example<'a> {
fn new(reference: &'a i32) -> Self {
Example { reference }
}

fn get_ref(&self) -> &'a i32 {
self.reference
}
}

In this struct Example, the lifetime 'a is used to ensure that the reference within the struct is valid for the lifetime 'a.

Summary

  • Lifetimes: They track the validity of references to ensure memory safety.
  • 'static Lifetime: Valid for the entire program duration.
  • Implicit Lifetimes: Inferred by the compiler based on simple rules.
  • Explicit Lifetimes: Specified by the programmer for more control over reference validity.
  • Lifetime Elision Rules: Simplify the need for explicit lifetime annotations in common cases.

Writer

The Write trait is part of Rust’s standard library (std::io) and provides methods for writing bytes to a stream. This is similar to writing to files, network sockets, or in-memory buffers.

Write Trait

Here are some common methods provided by the Write trait:

  • write: Writes a buffer of bytes to the underlying stream.
  • flush: Flushes any buffered data to the underlying stream.

MODULUS_BIT_SIZE

This indicates the size of the modulus of the finite field Fr in bits. The modulus is a prime number that defines the field, and MODULUS_BIT_SIZE tells us how many bits are needed to represent this prime number.

see where it defines

Absence of semicolon

In Rust, the absence of a semicolon at the end of an expression within a function signifies that the value of that expression is the return value of the function. This is an important aspect of Rust syntax.

[cfg(test)]

The #[cfg(test)] attribute is a conditional compilation attribute that tells the Rust compiler to include the annotated code only when running tests. It is commonly used to define modules, functions, or other code that should only be compiled and run during testing.

// Main code
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn subtract(a: i32, b: i32) -> i32 {
    a - b
}

// Test code
#[cfg(test)]
mod addition_tests {
    use super::*;

    #[test]
    fn test_add_positive_numbers() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn test_add_negative_numbers() {
        assert_eq!(add(-2, -3), -5);
    }
}

#[cfg(test)]
mod subtraction_tests {
    use super::*;

    #[test]
    fn test_subtract_positive_numbers() {
        assert_eq!(subtract(5, 3), 2);
    }

    #[test]
    fn test_subtract_negative_numbers() {
        assert_eq!(subtract(-5, -3), -2);
    }
}

[rustfmt::skip]

The #[rustfmt::skip] attribute is used to instruct rustfmt, the Rust formatting tool, to skip formatting the annotated item. This is useful in cases where automatic formatting might make the code less readable or where specific formatting is desired for certain blocks of code.

#[derive(...)] attribute

The #[derive(...)] attribute in Rust is a way to automatically implement certain common traits for a struct or enum.

Common Generic Type Parameter Conventions

Leave a Reply

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