🌱
Dev Compendium
  • Ethereum
    • Solidity
      • EVM
      • Architecture
      • Execution Context
      • Transactions
      • Gas
      • Calldata, Memory & Storage
      • Gas Optimisation
      • Function Declarations
      • receive() & fallback()
      • CALL vs. DELEGATE CALL
    • Yul
      • Introduction
      • Types
      • Basic Operations
      • Storage
      • Memory
        • Arrays
        • Structs
        • Tuples, revert, keccak256
        • Logs and Events
        • Gotchas
        • abi.encode
      • Calldata
        • External Calls
        • Dynamic Length Inputs
        • Transferring Value
        • Receiving Contract Calls
      • Contracts in Yul
      • Other Yul Functions
    • Foundry
    • Security
      • Common Vulnerabilities
      • Best Practices
      • Development Workflow
      • Contract Migration
    • Auditing Tools
      • Slither
      • Mythril
      • Fuzzing
    • Upgradable Contracts
      • Upgrade Patterns
      • ERC-1967 Implementation
      • Deployment
    • MEV
    • Tooling
      • Chainlink
      • IPFS
      • Radicle
    • Frontend
      • Contract Hooks
      • Wallet Connection
        • wagmi.sh
        • Rainbow Kit
      • thirdweb
    • Protocol Research
      • Uniswap v2
      • Uniswap v3
      • Curve
      • GMX
  • Starkware
    • Fundamentals
    • Account Abstraction
    • Universal Deployer
    • Cairo 1.0
    • starknet.js
    • Security Model
  • Zero Knowledge
    • Group Theory
    • ECDSA
  • Rust
    • Basic Operations
    • Set up
    • Primitives
    • Control Flow
    • Mutability & Shadowing
    • Adding Behavior
    • Lifetimes
    • Std Library
  • SUI
    • Architecture
    • Consensus Mechanism
    • Local Node Setup
    • Sui Client CLI
    • Move Contracts
      • Move
      • Move.toml
      • Move.lock
      • Accessing Time in Sui Move
      • Set up Development Framework
      • Debug & Publish
      • Package Upgrades
      • Sui Move Library
      • Difference from Core Move
    • Object Programming
      • Object Basics
      • Using Objects
      • Immutable Objects
      • Object Wrapping
      • Dynamic Fields
      • Collections
      • Unit Testing
      • Deployment with CLI
  • NEAR
    • Architecture
    • Contract Standards
      • Fungible Token (NEP-141)
      • Non-Fungible Token (NEP-171)
      • Storage Management (NEP-145)
      • Events (NEP-297)
      • Meta-Transactions
    • Rust Contracts
      • Development Workflow
      • Smart Contract Layout
      • Storage Management
      • Events & Meta-transactions
      • Method Types
      • Upgrading Contracts
      • Unit Testing
    • NEAR Libraries
    • Environment Variables
    • Serialisation
    • Security Concepts
    • Collections
    • JS SDK
Powered by GitBook
On this page
  1. Rust

Primitives

PreviousSet upNextControl Flow

Last updated 1 year ago

The language

The default “Hello, world!” program in Rust looks something like this:

fn main() { println!("Hello, world!"); }

If you’re already familiar with languages like C, C++, Java, etc., this sort of boilerplate should feel natural:

  • The default program entrypoint is a function called main. It takes no arguments and has no return type.

  • The body of the function is enclosed in curly braces.

  • The contents of the function are:

    • A function call to println! with a single argument, the string "Hello, world!".

  • tform dependent: 32 bits on a 32-bit machine, 64 bits on a 64-bit machine, etc.)

  • f32 — float

  • f64 — double

Simple function definitions typically look like this:

fn function_name(param1: Type1, param2: Type2) -> ReturnType { // body }

where the parameter list (everything inside the round brackets (...)) and return type (everything between the closing parenthesis ) and the opening curly bracket {) is optional, depending on the needs of your function.

From here on, all of the code we’ll be discussing can go directly inside the main function body (although some things, like structs and other function definitions, can, and are usually recommended to be placed outside).

Basic bindings and numeric primitives

The first thing we’ll look at is variable bindings.

let my_variable = 10;

(In addition to being valid Rust, this is also valid JavaScript—how about that!)

Though the syntax here is pretty simple, there’s a bit more going on under the hood.

Remember how Rust is a statically-typed programming language? Well, in this case, the type is not explicit in the code, but the compiler still assigns it one. In this case, the type of this variable is i32, which is the default for otherwise unrestricted number types. i32 means “32-bit signed integer”.

As you may have guessed, there are a bunch of other number types. Generally, the names take the form of <single-letter-prefix><bit-size>.

Prefix
Meaning
Possible Sizes

i

Signed integer

8, 16, 32, 64, 128, size

u

Unsigned integer

8, 16, 32, 64, 128, size

f

Floating-point

32, 64

Examples:

  • i8 — signed byte

  • u64 — unsigned 64-bit integer

  • usize — unsigned word-size integer (plaMore about bindings

We have three options to explicitly indicate the type of our binding:

  • let my_variable: i32 = 10;

    This syntax declares the type of the binding, and the expression is coerced to that type if possible.

  • let my_variable = 10 as i32;

    This syntax casts the expression to the given type, which the binding then assumes.

  1. let my_variable = 10i32;

    This syntax is unique to numeric literals, and it declares the type of the expression, which the binding then assumes.

Other primitives

Booleans

In addition to numeric types, Rust also has booleans:

`// This is a comment; ignored by the compiler // This type annotation is superfluous, but it is included for clarity. let happy_to_learn_rust: bool = true;

let ever_going_to_give_you_up = false;`

Characters

Single Unicode characters, delimited by single quotes ':

let currency_symbol: char = '$'; // Multi-byte characters allowed! let kanji_character: char = 'ĺ­—';

Strings

This can be a bit of a tricky topic in Rust, because there are a few different string types to think about. However, you usually only need to worry about two:

  • String is a heap-allocated string that can be mutated in-place. This is called an “owned string”.

  • &str is a fixed-length (sometimes stack-allocated) string that cannot be mutated. This can be called a “string slice” or “string reference”.

For now, you can convert between the two string types fairly easily:

let my_str: &str = "hello"; let my_string: String = my_str.to_string(); let another_str: &str = &my_string;

Arrays

Arrays are fixed-length, homogenous collections delimited by square brackets [] with elements separated by commas ,. Note the type signature takes the form of [<element-type>; <length>].

`let my_i32_array: [u32; 4] = [1, 2, 3, 4]; let my_bool_array: [bool; 0] = []; let my_char_array: [char; 3] = ['a', 'b', 'c'];

// Error: type signature has incorrect length // let incorrect_array: [char; 100] = [];`

Tuples

Tuples are fixed-length, heterogenous collections delimited by round brackets () with elements separated by commas ,.

let my_tuple: (i32, char, [bool; 2]) = (1, 'a', [true, false]); // It's probably easier to just let the compiler compute the type let my_tuple = (1, 'a', [true, false]);

Structs

To a programmer familiar with an object-oriented or classical style of programming, structs should feel familiar. They look and often feel very similar to a class or dictionary-esque value.

First, we define the type:

struct BlogPost { title: String, text: String, author: String, timestamp: u64, }

Then, to create an instance:

let mut post = BlogPost { title: "Rust quickstart for JavaScript programmers".to_string(), text: "So, you want to learn Rust, and fast...".to_string(), author: "Jacob Lindahl".to_string(), timestamp: 1600000000000, };

Member access:

`// read println!("{}", post.title);

// mutate post.author = "John Doe".to_string();`

Now, really, structs are just a way to give nice labels to pieces of a tuple. This is evident in some of the other ways you can declare a struct type:

struct MyTupleStruct(u8); struct RGBColor(u8, u8, u8); struct RGBAColor(u8, u8, u8, u8);

struct LiterallyNothing;

Enums

If you’re coming from a language like Java, the most basic enum will look pretty familiar:

enum LogLevel { Error, Warn, Info, Debug, }

However, Rust is just getting started. Each variant of an enum can contain data, just like a struct or a tuple.

enum ImageFilter { InvertColors, Blur(f64), HueRotate(f64), DropShadow { x: f64, y: f64, blur: f64, color: String, }, }

The power doesn’t end there: keep reading for more!

If the compiler is able to determine the appropriate type for your binding by itself, you do not need to specify it in the source code. Note that type declarations for function parameters and return types are always required (unless the function ).

(Technically this is a bit of an oversimplification, but it will get you 90% of the way there. Once we’ve gone over and ownership we can come back to this topic.)

If you’re familiar with , this is usually how it is implemented in Rust.

If you really want, you can also create :

While these may not initially seem to be useful, implementing some on them may change that.

. Although languages like Java and TypeScript offer a primitive form of enum, Rust’s are fully-featured sum types, implemented as tagged unions.

4
does not return anything
references
the newtype pattern
completely empty structs
traits
Rust’s enums are one of its most powerful features