Rise In Logo



Ankit Raj

Head of Growth at Rise In

May 14, 2024

Getting Started With Rust - A Beginner's Guide to Learning Rust

Rust for Beginners: From Setup to Your First Project



Rust has gained significant traction recently, serving as a multipurpose programming language in the developer community, providing high performance, memory safety, and concurrency bugs. Developed by Mozilla, it is a modern, systems-level programming language that is the most popular choice for developers working on various applications, from operating systems to web browsers.

Rust's Main Features and Advantages

Speed

Rust empowers developers to craft high-level code without compromising efficiency. It achieves this through zero-cost abstractions, rendering it an ideal language for creating fast and dependable software. Having its ownership system and the absence of a garbage collector. These properties enable the development of exceptionally fast programs, making Rust suitable for resource-intensive tasks and applications demanding minimal latency.

Memory Safety

Rust prioritizes memory safety through its ownership system and borrow checker. This robust approach eliminates entire categories of memory errors prevalent in other languages, including dangling pointers, null pointer dereferences, and buffer overflows. Consequently, Rust programs benefit from increased reliability and security, making them well-suited for critical systems.

Concurrency

Rust's ownership system extends to concurrency, fostering the development of safe and efficient concurrent programs. By leveraging ownership and borrowing principles, Rust eliminates the need for manual memory management in threads and ensures thread safety. This approach minimizes the risk of data races and other concurrency-related errors, simplifying the creation of robust multi-threaded applications.

Type Safety

Rust enforces a rigorous type system that guarantees data integrity by performing type checks at compile time. This proactive approach helps identify potential type mismatches early in the development cycle, preventing runtime errors and unexpected program behavior. Consequently, Rust programs benefit from enhanced robustness and maintainability.

Application Areas of Rust

Rust finds applications across a wide range of domains, including:

System Programming

Rust's unique blend of performance, memory safety, and fine-grained control over hardware resources makes it a compelling choice for system programming tasks. This includes the development of operating systems, device drivers, and networking protocols. The ability to write efficient, low-level code with robust memory guarantees positions Rust as a strong contender in system-level software development.

WebAssembly

Rust offers the ability to compile code to WebAssembly (Wasm). This empowers developers to create high-performance web applications that execute efficiently and securely within web browsers. By leveraging Rust's strengths in performance and safety, Wasm-compiled components can significantly enhance web applications' capabilities.

Embedded Systems

Rust proves to be a compelling choice for embedded systems development due to its inherent strengths. The language's minimal runtime footprint and fine-grained control over system resources enable developers to create resource-efficient code. Moreover, Rust's memory safety guarantees ensure program reliability, a critical aspect for embedded systems like microcontrollers and Internet of Things (IoT) devices.

Game Development

The gaming industry has shown a growing interest in Rust due to its performance characteristics and memory safety guarantees, which can help mitigate common game development issues like memory leaks and buffer overflows.

Blockchain and Cryptocurrencies

The security and concurrency features of Rust make it a popular choice for developing blockchain technologies and cryptocurrency applications. Whether you're building high-performance systems, web applications, or embedded devices, Rust provides the tools you need to write efficient and reliable code.

Why Should You Consider Learning Rust?

Rust is a rising star in the programming world, attracting developers with its focus on performance and reliability. Here's what makes Rust stand out:

Memory Safety Without Garbage Collection

Rust revolutionizes memory management by departing from the traditional approach employed by C and C++. Unlike these languages, which require developers to handle memory manually, Rust leverages a novel ownership model. This system enforces memory safety during compilation, eliminating the need for a garbage collector. This innovative approach eradicates entire categories of memory errors, including null pointer dereferences, buffer overflows, and data races, while preserving the performance benchmarks of lower-level languages.

Benefits of this System:

  • Fewer Errors: Rust helps developers write more robust and secure programs by catching memory errors at compile time.
  • Predictable Performance: Unlike garbage collection, which can cause unpredictable pauses, Rust's ownership system delivers deterministic performance, making it ideal for real-time systems.
  • No Garbage Collector Overhead: Without a garbage collector constantly cleaning up memory, Rust programs generally have lower memory overhead.

Concurrency Model: Preventing Data Races

Rust's concurrency model takes a unique approach to data races, a bug that's often likened to a programming error creating issues in multithreaded programs. Unlike other languages that rely on developers to meticulously guard access points, Rust leverages its ownership system to prevent data races at compile time. By enforcing ownership and borrowing rules, Rust ensures thread safety without the need for complex synchronization or manual locking mechanisms. This innovative approach frees programmers from the burden of battling data races, allowing them to focus on crafting elegant and robust concurrent applications.

Thriving Ecosystem & Supportive Community

Despite its relative youth, Rust boasts a thriving ecosystem and a highly supportive community. A wealth of well-crafted libraries and tools are readily available across diverse domains, encompassing web development, systems programming, and more. This extensive toolkit streamlines the development process. Moreover, the Rust community is renowned for its welcoming nature and eagerness to assist newcomers.


Bundle of online resources and active forums provides exceptional support for those seeking to learn and grow their Rust proficiency. The language undergoes continuous improvement through frequent updates and releases, ensuring developers always have access to the latest advancements.

High Demand for Rust Developers

The rise of Rust's popularity is fueling a surge in demand for developers proficient in the language. Rust's unique capabilities make it an increasingly attractive choice as companies prioritize performance, security, and concurrency in their systems. This demand spans various industries, from established tech giants to innovative startups. By acquiring Rust expertise, developers can unlock many career opportunities in cutting-edge technologies like high-performance web development, embedded systems, and blockchain, positioning themselves as valuable assets in a competitive job market.

Learning Rust is an investment in the future, offering developers a powerful tool for building robust, efficient, and secure software systems. With its unique approach to memory safety, concurrency, and performance, Rust empowers developers to tackle complex challenges while ensuring code reliability and maintainability.

Setting Up Your Rust Development Environment

Step 1 - Installing Rust with rustup

Rust provides a convenient installation tool called rustup that simplifies installing and managing Rust toolchains on various operating systems.

Windows

  • Visit the official Rust website here.
  • Under the "rustup" section, click the "rustup-init.exe" link to download the installer.
  • Run the downloaded executable and follow the on-screen instructions to complete the installation.

macOS

  • Open a terminal window.
  • Run the following command to download and install `rustup`:
    bash
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  • Follow the on-screen instructions to complete the installation.


Linux

  • Open a terminal window.
  • Update the package index:
    bash
    sudo apt-get update
  • Install the required dependencies:
    bash
    sudo apt-get install curl
  • Run the following command to download and install `rustup`:
    bash
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  • Follow the on-screen instructions to complete the installation.

After the installation, you can verify if Rust is installed correctly by running the following command in your terminal:

bash

rustc --version

This should display the installed Rust version.

Introducing Cargo

Cargo is Rust's package manager and build system. It manages dependencies, builds your project, and provides many other useful features.

To create a new Rust project, follow these steps:

  • Open a terminal and navigate to the directory where you want to create your project.
  • Run the following command to create a new binary project:
    bash
    cargo new my_project --bin


    Replace `my_project` with your desired project name.
  • Cargo will create a new directory with the project name and initialize a new Cargo package.
  • To build your project, navigate to the project directory and run:
    bashcargo build
    This command will compile your project and create an executable file in the `target/debug` directory.
  • To run your project, use the following command:
    bash
    cargo run
    Cargo will automatically build and execute your project.

These are the basic steps to get started with Rust development. As you continue exploring Rust, you'll learn more about Cargo's features, such as managing dependencies, running tests, and publishing your packages to crates.io (Rust's package registry).

Now that you have Rust and Cargo set up, let's explore how to integrate Rust development into your favorite tools. We'll also delve into some fundamental Rust concepts.

Configuring an Integrated Development Environment (IDE) or text editor for Rust development 

IDE or Text Editor for Rust?

  • Visual Studio Code (VS Code) is a popular choice due to its lightweight nature and extensive customization options. Install the "Rust" extension for syntax highlighting, code completion, and debugging support.

  • IntelliJ IDEA with Rust plugin: This tool offers a feature-rich environment for Rust development. Install the official Rust plugin for functionalities like code navigation, refactoring, and integration with Cargo commands.

Basic Rust Concepts 

Once you have your environment configured, it's time to explore core Rust concepts:

Variables and Data Types

Rust is statically typed unlike some languages. When defining a variable, you must declare the data type (like integers or strings). This ensures type safety and prevents errors at compile time.

let x: u32 = 42; // Unsigned 32-bit integer

let y: f64 = 3.14; // 64-bit floating-point number

Functions

Functions are reysable Blocks of code that perform specific tasks. They can accept arguments (inputs) and return values (outputs).

fn add(x: i32, y: i32) -> i32 {

    x + y

}

Control Flow

Control flow statements like if, else, loop, and while dictate how your program executes based on conditions and repetitions.

let x = 5;

if x > 0 {

    println!("Positive number");

} else {

    println!("Non-positive number");

}

Ownership and Borrowing

Rust's ownership and borrowing concepts are fundamental to its memory safety guarantees. Every value in Rust has a single owner, and when the owner goes out of scope, the value is automatically deallocated. Borrowing allows temporary access to a value without taking ownership.
fn main() {

    let s = String::from("hello"); // s is the owner of the String

    take_ownership(s); // s is moved, no longer valid

    println!("{}", s); // Error: s is no longer valid

}

fn take_ownership(s: String) { // s becomes the owner

    println!("{}", s);

} // s is dropped at the end of the function

Understanding Ownership

  • One Owner at a Time: In Rust, each piece of data can only have one owner at a time. This ownership is tied to variables.
  • Moving Ownership: When you assign a value to another variable, ownership is transferred (moved). The original variable can no longer be used. This prevents accidental use of freed memory.
  • Dropping Ownership: When a variable goes out of scope, its ownership is dropped, and the associated memory is automatically freed.

Borrowing allows temporary access to data owned by another variable without ownership transfer. There are two main types of borrows:

  • Immutable Borrows (&):  Provide read-only access to borrowed data. You can't modify the borrowed data through an immutable borrow.
  • Mutable Borrows (&mut):  Allow modification of the borrowed data. However, only one mutable borrow can exist for a piece of data at a time, ensuring exclusive access and preventing data races (concurrent modifications).

Example : 

let x = 5; // x owns the value 5
let y = x;  // Ownership is moved to y, x is no longer usable  
let mut numbers = vec![1, 2, 3]; // numbers owns the vector data
let first = &numbers[0];        // Immutable borrow to the first element
let mut last = &mut numbers[2];   // Mutable borrow to modify the last element
// first is immutable, so you can't modify numbers[0] through it
// last allows modification, so you can update the value in numbers

Variables and Data Types

  • Primitive Types: Rust offers various built-in data types like integers (i32, u64), booleans (bool), floating-point numbers (f32, f64), and characters (char).
  • Strong Type System: Variables must be explicitly declared with their data type, ensuring type safety and preventing errors at compile time. For example, you can't accidentally assign a string value to an integer variable.
  • Type Inference: Rust can often infer the data type of a variable based on the value assigned to it, reducing the need for explicit type declarations in some cases.
  • Ownership: Each variable in Rust "owns" the data it holds. When the variable goes out of scope, the memory is automatically freed. This concept helps prevent memory leaks and dangling pointers.

Structs and Enums

Structs

Define custom data structures to group related data fields under a single name. This helps organize your code and improve readability.

Enums

Represent multiple possible data types within a single variable. Use them to create flexible and efficient data structures.

Diving Deeper into Rust : Intermediate Topics

Functions

In Rust, functions are declared using the fn keyword, followed by the function name, parameters (if any), and an optional return type.

Function Declaration

fn function_name(param1: Type1, param2: Type2, ...) -> ReturnType {

    // function body

}

fn is the keyword to define a function.

function_name is the name of the function, following Rust's naming conventions (snake_case).

param1, param2, etc., are the parameter names, and Type1, Type2, etc., are their respective types.

-> ReturnType specifies the type of the value returned by the function. If the function does not return a value, the return type is omitted.


Function Parameters

Function parameters are specified in the function declaration, with each parameter having a name and a type.

fn add(x: i32, y: i32) -> i32 {

    x + y

}

In this example, add is a function that takes two i32 (32-bit signed integer) parameters, x and y, and returns their sum, which is also an i32.

Return Values

Functions in Rust can return values using the return keyword, followed by the value to be returned. If a function has a return type specified, the returned value must match that type.

fn double(x: i32) -> i32 {

    return x * 2;

}

If the last expression in a function body is not followed by a semicolon, it is automatically returned without the need for the return keyword.

fn triple(x: i32) -> i32 {

    x * 3

}


Error Handling

Rust's approach to error handling emphasizes reliability and safety. The Option and Result types are the primary tools for handling potential errors and exceptional cases.


Option Type

The Option type represents an optional value that may or may not exist. It has two variants:

Some(T): Represents a valid value of type T.

None: Represents the absence of a value.

Example usage:



let x: Option<i32> = Some(42);

let y: Option<i32> = None;

match x {

    Some(value) => println!("Value: {}", value),

    None => println!("No value"),

}

match y {

    Some(value) => println!("Value: {}", value),

    None => println!("No value"),

}

Output -

Value: 42

No value

Result Type

The Result type represents the outcome of an operation that may succeed or fail. It has two variants:

  • Ok(T): Represents a successful result containing a value of type T.
  • Err(E): Represents a failed result with an error value of type E.

Example usage:

fn divide(x: i32, y: i32) -> Result<i32, String> {

    if y == 0 {

        Err(String::from("Division by zero"))

    } else {

        Ok(x / y)

    }

}

let result = divide(10, 2);

match result {

    Ok(value) => println!("Result: {}", value),

    Err(error) => println!("Error: {}", error),

}

let result = divide(10, 0);

match result {

    Ok(value) => println!("Result: {}", value),

    Err(error) => println!("Error: {}", error),

}


Output -

Result: 5

Error: Division by zero

By using Option and Result, Rust encourages developers to handle errors explicitly, increasing code reliability and preventing common pitfalls like null pointer dereferences or uncaught exceptions.

Generics and Lifetimes

Generics and lifetimes are Rust features that enable writing flexible and reusable code.

Generics

Generics allow you to define functions, structs, enums, and traits that can work with multiple types. This promotes code reuse and avoids duplication.

Example usage:

fn print_value<T: std::fmt::Display>(value: T) {

    println!("Value: {}", value);

}

print_value(42); // Works with i32

print_value(3.14); // Works with f64

print_value("Hello, World!"); // Works with &str

In this example, the print_value function is generic over the type T, which must implement the std::fmt::Display trait to be printable.

Lifetimes

Lifetimes are a way to ensure memory safety when working with references. They describe the scope for which a reference is valid.

Example usage:

fn longest_string<'a>(x: &'a str, y: &'a str) -> &'a str {

    if x.len() > y.len() {

        x

    } else {

        y

    }

}

let string1 = String::from("hello");

let string2 = String::from("world");

let result = longest_string(&string1, &string2);

println!("The longest string is: {}", result);

In this example, the longest_string function takes two string slices (&str) and returns the longer one. The lifetime annotation 'a ensures that the returned reference has the same lifetime as the input references. Generics and lifetimes are powerful features that enable writing efficient, reusable, and safe code. They are essential concepts in Rust, allowing developers to create flexible and robust solutions while maintaining Rust's strong guarantees around memory safety and performance.

Creating a Simple Command-Line Application in Rust

Step 1: Open your terminal and create a new Rust project using Cargo:

cargo new cli_app


Step 2: Navigate into the project directory:

cd cli_app


Step 3: Open the src/main.rs file in your preferred code editor.


Step 4: Replace the default code with the following:

use std::env;

fn main() {

    let args: Vec<String> = env::args().collect();

    if args.len() < 2 {

        println!("Please provide a name as an argument.");

        return;

    }

    let name = &args[1];

    println!("Hello, {}!", name);

}

This program reads the command-line arguments and prints a greeting with the first argument as the name.


Step 5 Build and run the application:

cargo run -- John


This should output:

Hello, John!

Creating a Basic Web Server in Rust

Step 1: Open your terminal and create a new Rust project using Cargo:

cargo new web_server

Step 2: Navigate into the project directory:

cd web_server

Step 3: Add the necessary dependencies to your Cargo.toml file:

[dependencies]

rocket = "0.5.0-rc.2"

Step 4: Create a new file called main.rs in the src directory with the following content:

#[macro_use] extern crate rocket;

#[get("/")]

fn index() -> &'static str {

    "Hello, World!"

}

#[launch]

fn rocket() -> _ {

    rocket::build().mount("/", routes![index])

}

This code defines a simple route that returns the string "Hello, World!" when accessed at the root URL.

Step 5: Build the application:

cargo build

Step 6: Run the application:

cargo run

Step 7: Open your web browser and navigate to http://localhost:8000. You should see the "Hello, World!" message displayed.

The web server is now running and listening for incoming requests on http://localhost:8000. You can modify the index function to return different content or add more routes as needed.

Introduction to external crates (libraries) to extend functionality.

Rust offers a rich ecosystem of pre-written code called crates, available on the crates.io registry. These crates provide functionality you can integrate into your projects, saving you time and effort. Additionally, robust debugging and testing practices are crucial for writing reliable Rust applications.

External Crates: Boosting Capabilities

  • Finding Crates: Explore crates.io to discover crates for various purposes like network communication, data manipulation, or user interface development.
  • Adding Crates to Your Project: Use the cargo add command followed by the crate's name. This downloads the crate and adds it to your project's dependencies.
  • Using Crate Functionality: Import the crate using the use keyword and access its functions and types within your code.

Debugging and Testing 

Debugging Tools

Rust provides a built-in debugger (gdb) for examining your program's state during execution. Additionally, the println! macro allows you to print values and statements at specific points in your code to track its behavior.

Testing

Unit tests isolate and test individual components of your program. Tools like cargo test can automate running and verifying test cases. Consider frameworks like est or criterion for more advanced testing scenarios.

Debugging and testing are essential for producing high-quality, reliable Rust applications. By leveraging the built-in tools and embracing a testing-focused mindset, you can catch and fix issues early, ensuring your code works as intended.

Benefits of Crates and Testing:

  • Code Reusability:  Leverage pre-written and tested code from crates, saving development time and effort.
  • Reduced Errors:  Crates often undergo rigorous testing, reducing the risk of bugs in your own code.
  • Improved Quality:  Testing practices identify and fix issues early on, leading to more robust and reliable applications.

Advanced Rust

Rust supports asynchronous programming, a paradigm that enables applications to execute tasks concurrently without hindering the main thread. This approach fosters efficient resource utilization and enhances responsiveness. Rust's async and await keywords provide a structured and clear way to write non-blocking, asynchronous code.

Traits 

Traits and the type system are cornerstones of Rust's approach to code safety, flexibility, and reusability. Traits define behaviors that types can implement. Similar to interfaces in other languages. Traits promote code reuse and abstraction by allowing multiple types to fulfill the same functionality. However, Rust's traits offer additional power through features like associated types (defining types related to the trait) and default implementations (providing default behavior for methods).

Type System

The type system, enforced throughout your code, ensures type safety. It leverages advanced features like generics (functions or structs that work with various data types) to enhance code reusability without compromising safety. Lifetimes, another type system feature, track reference scopes, preventing memory-related errors.
Together, traits and the type system empower you to write expressive, concise, and robust Rust programs.

Concurrency in Rust

Rust empowers developers to write concurrent programs, where multiple tasks can seemingly execute simultaneously. This capability is crucial for leveraging the processing power of modern multi-core processors.
Rust offers various mechanisms to achieve concurrency, all emphasizing safety and preventing common pitfalls like data races and deadlocks

Threads

Threads are the foundation of concurrency in Rust. They represent independent units of execution within a program, allowing you to run multiple tasks concurrently. This is ideal for CPU-bound tasks that can benefit from parallel processing.

Channels

Channels provide a safe and efficient way for threads to communicate and exchange data. Unlike directly accessing shared memory, channels eliminate the risk of data races by ensuring only one thread can access the data at a time. Data is sent through a sender and received through a receiver.

Async/Await

While threads excel at handling CPU-bound tasks, asynchronous programming with async/await is better suited for I/O-bound operations like network requests or file access. This approach avoids creating and managing numerous threads, which can be resource-intensive. async/await combined with libraries like Tokio (a popular asynchronous runtime) allows you to write code that efficiently manages a high volume of concurrent I/O operations.

Benefits of Rust 

  • Rust provides a safe and efficient way to achieve concurrency through threads, channels, and async/await.
  • Threads are ideal for CPU-bound tasks, while async/await is better suited for I/O-bound operations.
  • Channels ensure safe communication between threads, preventing data races.
  • Understanding these concurrency features is essential for building robust and efficient concurrent systems in Rust.

Understanding Unsafe Rust: When and How to Use It

Rust is designed to be a safe systems programming language, ensuring memory safety and preventing common bugs like null pointer dereferences, data races, and buffer overflows. However, there are situations where you might need to bypass Rust's safety guarantees and work with lower-level constructs. This is where unsafe Rust comes into play. Rust prioritizes memory safety and prevents common programming errors. However, there are situations where controlled detours from these safety checks are necessary. Unsafe Rust, enabled by the unsafe keyword, provides this capability.

When to Consider Unsafe Rust

Unsafe Rust should be used judiciously and with caution. It is typically needed in the following scenarios:

  • Interfacing with External Code: When working with C libraries or system calls that don't follow Rust's safety model, unsafe code acts as a bridge between these external elements and Rust's safe world.
  • Low-Level System Programming: Certain low-level operations, like dereferencing raw pointers or using inline assembly, necessitate unsafe code due to potential safety violations if used improperly.
  • Performance Optimization: In specific scenarios, unsafe code might be used to achieve optimal performance, such as manual memory management or using intrinsics (low-level CPU instructions).
  • Implementing Unsafe Abstractions: Creating safe abstractions that rely on unsafe code internally (custom allocators or concurrent data structures) requires unsafe code to ensure the overall correctness and safety of the abstraction.

Exercising Caution with Unsafe Rust

  • Sparingly and Only When Necessary: Strive to write safe Rust code whenever possible. Unsafe Rust should be a last resort, used judiciously and with a clear understanding of the implications.
  • Isolating Unsafety: Confine unsafe code blocks within the unsafe keyword to minimize the potential impact of errors.
  • Thorough Documentation: Clearly document the purpose and assumptions behind any unsafe code, explaining the rationale and potential hazards for better understanding by others.
  • Comprehensive Testing: Write unit tests and integration tests to ensure the correctness and stability of unsafe code, considering edge cases and potential failure scenarios.
  • Code Review: Conduct rigorous code reviews for unsafe code to identify potential issues, validate assumptions, and ensure adherence to safety guidelines.

Resources for Learning and Staying Updated

  • Rust's official resources and the vibrant community provide a wealth of information to support your learning journey and keep you updated with the latest developments.
  • Click here to access the official learning resources.
  • Explore the Rust Book, a comprehensive guide to the Rust programming language.
  • Refer to the Rust Standard Library Documentation for detailed information about the standard library.
  • Check the Rust by Example book for concise, example-driven explanations of Rust concepts.
  • Do check out this comprehensive Rust Course by Rise In.

Staying Updated with Rust

  • Subscribe to the This Week in Rust newsletter to receive weekly updates on the latest Rust news, releases, and blog posts.
  • Follow the Rust Blog for announcements, updates, and discussions from the Rust team.
  • Monitor the Rust GitHub repository and its associated repositories for upcoming changes and new features.

Contributing to Rust

Rust's ecosystem thrives on community contributions. Whether you're a seasoned developer or a newcomer, there are opportunities to contribute to Rust's growth and development.

Contributing to Rust's Open-Source Libraries

  • Identify a Rust library you're interested in and explore its GitHub repository.
  • Check the project's contributing guidelines and open issues to find suitable tasks or feature requests to work on.
  • Fork the repository, make your changes, and submit a pull request for review.

Contributing to Rust's ecosystem is a rewarding experience that helps shape the future of the language and its ecosystem. By engaging with the community, learning from resources, and contributing your knowledge and skills, you can actively participate in Rust's growth and success.

Rust: A Valuable Skill for the Future

Rust's unique combination of performance, safety, and concurrency makes it a compelling choice for various applications, from systems programming and embedded devices to web development and blockchain technologies. Its focus on memory safety and data race prevention addresses critical issues that have plagued traditional systems programming languages, leading to more secure and reliable software.


Continuous learning and practice are key to mastering any programming language, and Rust is no exception.Seek out challenging problems, and don't hesitate to ask questions or seek guidance from the welcoming Rust community. Rust has gained significant traction recently, serving as a multipurpose programming language in the developer community, providing high performance, memory safety, and concurrency bugs. Developed by Mozilla, it is a modern, systems-level programming language that is the most popular choice for developers working on various applications, from operating systems to web browsers.

Stay in the know

Never miss updates on new programs and opportunities.

Rise In Logo

Rise together in web3!