welcome소개

rust1

Q&A & 질문하기 & 기여하기

물어보고 싶거나 하고 싶은말 써 주세요comment

Appendix

Embebded|🔝|

CS & OS|🔝|

Assembly|🔝|

justfile 러스트로 만든 makefile비슷한거

link


Rust(justfile)|🔝|


project_name := `basename "$(pwd)"`

# cargo run
r:
    cargo r

# (optimization)cargo run --release
rr:
    cargo run --release

# cargo watch(check & test & run)
w:
    cargo watch -x check -x test -x run

# cargo watch(simple only run)
ws:
    cargo watch -x 'run'

# cargo check(Test Before Deployment)
c:
    cargo check --all-features --all-targets --all

# cargo test
t:
    cargo t

# cargo expand(test --lib)
tex:
    cargo expand --lib --tests

# cargo test -- --nocapture
tp:
    cargo t -- --nocapture

# nightly(cargo nextest run)
tn:
    cargo nextest run

# nightly(cargo nextest run --nocapture)
tnp:
    cargo nextest run --nocapture

# macro show(cargo expand)
ex:
    cargo expand

# emit mir file
mir:
    cargo rustc -- -Zunpretty=mir > target/{{project_name}}.mir

# emit asm file
es:
    cargo rustc -- --emit asm=target/{{project_name}}.s

# optimized assembly
eos:
    cargo rustc --release -- --emit asm > target/{{project_name}}.s

# emit llvm-ir file
llvm:
    cargo rustc -- --emit llvm-ir=target/{{project_name}}.ll

# emit hir file
hir:
    cargo rustc -- -Zunpretty=hir > target/{{project_name}}.hir

# cargo asm
asm METHOD:
    cargo asm {{project_name}}::{{METHOD}}

# clean file
cf:
    rm -rf target ./config rust-toolchain.toml *.lock

# nightly setting(faster compilation)
n:
    rm -rf .cargo rust-toolchain.toml
    mkdir .cargo
    echo "[toolchain]" >> rust-toolchain.toml
    echo "channel = \"nightly\"" >> rust-toolchain.toml
    echo "components = [\"rustfmt\", \"rust-src\"]" >> rust-toolchain.toml
    echo "[build]" >> .cargo/config.toml
    echo "rustflags = [\"-Z\", \"threads=8\"]" >> .cargo/config.toml

# .gitignore setting
gi:
    echo "# Result" >> README.md
    echo "" >> README.md
    echo "\`\`\`bash" >> README.md
    echo "" >> README.md
    echo "\`\`\`" >> README.md
    echo "" >> README.md
    echo "# Visual Studio 2015/2017 cache/options directory" >> .gitignore
    echo ".vs/" >> .gitignore
    echo "" >> .gitignore
    echo "# A collection of useful .gitignore templates " >> .gitignore
    echo "# https://github.com/github/gitignore" >> .gitignore
    echo "# General" >> .gitignore
    echo ".DS_Store" >> .gitignore
    echo "dir/otherdir/.DS_Store" >> .gitignore
    echo "" >> .gitignore
    echo "# VS Code files for those working on multiple tools" >> .gitignore
    echo ".vscode/" >> .gitignore
    echo "# Generated by Cargo" >> .gitignore
    echo "# will have compiled files and executables" >> .gitignore
    echo "debug/" >> .gitignore
    echo "target/" >> .gitignore
    echo "" >> .gitignore
    echo "# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries" >> .gitignore
    echo "# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html" >> .gitignore
    echo "Cargo.lock" >> .gitignore
    echo "" >> .gitignore
    echo "# These are backup files generated by rustfmt" >> .gitignore
    echo "**/*.rs.bk" >> .gitignore
    echo "" >> .gitignore
    echo "# MSVC Windows builds of rustc generate these, which store debugging information" >> .gitignore
    echo "*.pdb" >> .gitignore
    echo "" >> .gitignore
    echo "# WASM" >> .gitignore
    echo "pkg/" >> .gitignore
    echo "/wasm-pack.log" >> .gitignore
    echo "dist/" >> .gitignore

zig(justfile)|🔝|


C언어(justfile)|🔝|

C언어_컴파일파일이 다수(justfile)|🔝|


C++언어(justfile)|🔝|

C++언어_컴파일파일이 다수(justfile)|🔝|


Kotlin언어(justfile)|🔝|

Kotlin언어_컴파일파일이 다수(justfile)|🔝|

Rust OS 프로젝트

Rust 로 OS만들고 있는 대표적인 프로젝트 2개 (Redux & Mastro)

Redux

Mastro ( Unix-like kernel written in Rust) blog.lenot.re


Rust Linux 프로젝트


개발 로드맵&참고서적

link

game-programmer

Chapter 1(코딩의 원리)

  • 코드는 바이트 덩어리 이다. 요즘 컴퓨터는 64bits로 처리하기 때문에 64bits를 한덩어리로 봐야한다

  • 컴퓨터의 최적화 & 코드 만드는 원리의 기초는 CS(ComputerSystem)OS(OperatingSystem)다. 기초 공부를 튼튼히 해야 고급에서도 무너지지 않는다.

    • 어차피 사람이 만든거다. 어려운건 없다. 천천히 하면 된다.
    • 안된다 생각하면 진짜 안된다. 나는 할수 있다. 100번씩 외치면서 기본부터 차근차근하자.
    • 코딩은 하루아침에 안된다. 무슨일이든 10년은 해야 그때쯤 겨우 뭔가 나온다.(장인정신!! 우리는 예술가다.)

현대 언어는 AI와 결합한 코딩을 해야 살아 남는다. 무조건 AI를 활용하도록 하자

link


Rust 러스트 언어와 다른 언어의 차이점

러스트는 statement와 expression차이를 잘 알아야 한다.|🔝|

// Rust

fn bis(x: i32, y: i32) {
  // expression
  x | y
}

  • C언어 스타일
    • 마지막에 (;) 로 끝이 났다면 문장이 끝나서 statement라고 부른다.
    • 한글로 잘 몰라서 영어로 썼다. 구글로 검색할때도 영어로 쳐야 더 풍부한 정보가 나오니 이해 바란다.
// C

int bis(int x, int m) {
    // statement
    return x | m;
}

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

대부분의 코딩의 공통적인것들

link



모든언어는 비슷한 패턴이 있다.

Reserved Word(예약어)|🔝|

  • 어떤 언어를 시작하든 처음에 할일은 예약어를 우선 찾는다. aka. 예약어로 변수를 선언을 할 수 없다.

array(인덱싱에 최고)|🔝|

  • 2d, 3d matrix만들때 쓰는 아주 아주 재주가 많은 친구
let my_2d_array = [
    [1, 2], //
    [3, 4]  //
];

my_2d_array[0][1] = 2;

let my_array [0,1,2,3,4];

my_array[1] = 1;

vector 이건 array인데 확장 가능한 array이다.|🔝|

  • 모든 알고리즘에 99프로는 쓰는 vector 제일 많이 공부해야한다.
let my_array = [0; 3];

let my_vector = vec![0, 0, 0];

tuple 빠르다.|🔝|

  • API 실행하거나 명령어 불러올때 쓰는 버튼 같은 기능() 이런거 많이 보셨죠?? 결국 튜블임.
let my_tuple = (1, "test");

fn rust에서는 function을 선언할때 이렇게 붙힌다.|🔝|

fn main () {}

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

zig언어

  • zig는 C언어와 거의 비슷하다. 단순하고 쉽다.

link



array(Zig & Rust)|🔝|

// zig
const std = @import("std");
const print = std.debug.print;

pub fn main() !void {
    // Declare a 2D array of integers
    const matrix: [3][3]i32 = [_][3]i32{
        [_]i32{ 1, 2, 3 },
        [_]i32{ 4, 5, 6 },
        [_]i32{ 7, 8, 9 },
    };

    // Print the matrix elements
    for (matrix) |row| {
        for (row) |element| {
            print("{} ", .{element});
        }
        print("\n", .{});
    }
}
  • Result
$ zig build run

1 2 3
4 5 6
7 8 9

  • Rust
// Rust

fn main() {
    // Declare a 2D array of integers
    let matrix: [[i32; 3]; 3] = [
        //
        [1, 2, 3], //
        [4, 5, 6], //
        [7, 8, 9], //
    ];

    // Print the matrix elements
    for row in matrix {
        println!("{:?}", row)
    }
}
  • Result
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

for(Zig & Rust)|🔝|

  • Rust
// Rust
fn main() {
    for i in 0..4 {
        println!("{}", i);
    }

    let mut array = [1, 2, 3];

    // Iterate over the array by value
    for elem in &array {
        println!("by val: {}", elem);
    }

    // Iterate over the array by mutable reference
    for elem in &mut array {
        *elem += 100;
        println!("by ref: {}", elem);
    }

    let array02 = [1, 2, 3];
    // Iterate over the array with both value and mutable reference
    for (val, ref elem) in array02.iter().zip(array.iter_mut()) {
        println!("ele {}, val = {}", val, elem)
    }

    // Iterate over the array with index and value
    for (i, elem) in array.iter().enumerate() {
        println!("{}: {}", i, elem);
    }

    // Iterate over the array (no-op)
    for i in &array {
        println!("{i}");
    }

    
}
  • Result
0
1
2
3
by val: 1
by val: 2
by val: 3
by ref: 101
by ref: 102
by ref: 103
ele 1, val = 101
ele 2, val = 102
ele 3, val = 103
0: 101
1: 102
2: 103
101
102
103
// zig
const std = @import("std");
const print = std.debug.print;

pub fn main() !void {
    var array = [_]u32{ 1, 2, 3 };

    for (array) |elem| {
        print("by val: {}\n", .{elem});
    }

    for (&array) |*elem| {
        elem.* += 100;
        print("by ref: {}\n", .{elem.*});
    }

    for (array, &array) |val, *ref| {
        _ = val;
        _ = ref;
    }

    for (0.., array) |i, elem| {
        print("{}: {}\n", .{ i, elem });
    }

    for (array) |_| {}
}
  • Result

by val: 1
by val: 2
by val: 3
by ref: 101
by ref: 102
by ref: 103
0: 101
1: 102
2: 103

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

C++언어

link



array(C++ & Rust)|🔝|

// C++
#include <iostream>

int main() {
    // Declare a 2D array of integers
    int matrix[3][3];

    // Initialize the matrix elements
    matrix[0][0] = 1;
    matrix[0][1] = 2;
    matrix[0][2] = 3;
    matrix[1][0] = 4;
    matrix[1][1] = 5;
    matrix[1][2] = 6;
    matrix[2][0] = 7;
    matrix[2][1] = 8;
    matrix[2][2] = 9;

    // Print the matrix elements
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            std::cout << matrix[i][j] << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}
  • Result

$ g++ -std=c++2b -pedantic -pthread -pedantic-errors -lm -Wall -Wextra -ggdb -o ./target/main ./src/main.cpp
./target/main

1 2 3
4 5 6
7 8 9
  • Rust
// Rust

fn main() {
    // Declare a 2D array of integers
    let matrix: [[i32; 3]; 3] = [
        //
        [1, 2, 3], //
        [4, 5, 6], //
        [7, 8, 9], //
    ];

    // Print the matrix elements
    for row in matrix {
        println!("{:?}", row)
    }
}
  • Result
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

for문(C++ & Rust)|🔝|

  • modern c++에서는 begin, end를 활용하겠지만 단순한 비교를 위해 옛날 스타일로 비교한다.
// c++
#include <iostream>

int main() {
    for(int i =0; i< 10; ++i) {
        std::cout << i << std::endl;
    }
    return 0;
}
  • Rust
// Rust
fn main() {
    for i in 0..10 {
        println!("{}", i);
    }
}
  • Result
0
1
2
3
4
5
6
7
8
9

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

C언어

link



array(C & Rust)|🔝|

#include <stdio.h>

int main() {
    // Declare a 2D array of integers
    int matrix[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

    // Print the matrix elements
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }

    return 0;
}

clang -pedantic -pthread -pedantic-errors -lm -Wall -Wextra -ggdb -o ./target/main ./src/main.c
./target/main

1 2 3
4 5 6
7 8 9

  • Rust
// Rust

fn main() {
    // Declare a 2D array of integers
    let matrix: [[i32; 3]; 3] = [
        //
        [1, 2, 3], //
        [4, 5, 6], //
        [7, 8, 9], //
    ];

    // Print the matrix elements
    for row in matrix {
        println!("{:?}", row)
    }
}
  • Result
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

C언어 공식문서

link


Revision ISO publication Similar draft

C2x Not available N3096 [2023-04-02]

C17 ISO/IEC 9899:2018 N2310 [2018-11-11] (early C2x draft)

C11 ISO/IEC 9899:2011 N1570 [2011-04-04]

C99 ISO/IEC 9899:1999 N1256 [2007-09-07]

C89 ISO/IEC 9899:1990 Not available

ISO/IEC 9899:TC3(C89 & C99)고대 언어 느낌이네 ㅋㅋ

Kotlin언어

link



array(Kotlin & Rust)|🔝|

fun main() {
    // Declare a 2D array of integers
    val matrix = arrayOf(
        intArrayOf(1, 2, 3),
        intArrayOf(4, 5, 6),
        intArrayOf(7, 8, 9)
    )

    // Print the matrix elements
    for (row in matrix) {
        println(row.joinToString(" "))
    }
}
  • Result

kotlinc ./src/Main.kt -include-runtime -d ./out/Main.jar
java -jar ./out/Main.jar

1 2 3
4 5 6
7 8 9
  • Rust
// Rust

fn main() {
    // Declare a 2D array of integers
    let matrix: [[i32; 3]; 3] = [
        //
        [1, 2, 3], //
        [4, 5, 6], //
        [7, 8, 9], //
    ];

    // Print the matrix elements
    for row in matrix {
        println!("{:?}", row)
    }
}
  • Result
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

Swift언어

link



array(Swift & Rust)|🔝|

import Foundation

func main() {
    // Declare a 2D array of integers
    let matrix = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ]

    // Print the matrix elements
    for row in matrix {
        for element in row {
            print(element, terminator: " ")
        }
        print()
    }
}

main()

# MyCliAdd 라는 프로젝트로 프로젝트 만들기
# main.swift 에서 코드 작업하면 된다.
$ swift package init --name MyCLIAdd --type executable
Creating executable package: MyCLIAdd
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/main.swift


$ swift run
Building for debugging...
[8/8] Applying MyCLIAdd
Build of product 'MyCLIAdd' complete! (3.22s)
1 2 3
4 5 6
7 8 9

  • Rust
// Rust

fn main() {
    // Declare a 2D array of integers
    let matrix: [[i32; 3]; 3] = [
        //
        [1, 2, 3], //
        [4, 5, 6], //
        [7, 8, 9], //
    ];

    // Print the matrix elements
    for row in matrix {
        println!("{:?}", row)
    }
}
  • Result
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

C#언어



LINQ(C#의 꽃)

Query(LINQ관련)

// Specify the data source.
int[] scores = [97, 92, 81, 60];

// Define the query expression.
IEnumerable<int> scoreQuery =
    from score in scores
    where score > 80
    select score;

// Execute the query.
foreach (var i in scoreQuery)
{
    Console.Write(i + " ");
}

// Output: 97 92 81

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

Rust의 전체적인 그림 잡기

link


Stack & Heap 메모리 이해하기|🔝|

  • Stack은 고드름처럼 쌓여서 내려가고 Heap은 낮은 메모리 주소에 쌓여 들어간다.생각하면 됩니다. 낮은 메모리 주소에는 부팅에 필요한 코드 같은게 들어가니 충분히 공부한 후에 할당하세요.

    • Stack은 테트리스처럼 틈이 없이 쌓는 개념입니다.(embedded code에 사용되죠)
  • Stack과 Heap은 개발자가 옛날에 정한 약속 같은거라 왼쪽은 stack / 오른쪽은 heap하자 느낌으로 기억하자]()

  • 그림으로 이해해보자 RAM모양을 내 맘대로 정했다. 이게 외우기 젤 좋다.


  • stack vs heap
Stack vs Heap
컴파일 시간 결정
Stack is allocated at runtime;
layout of each stack frame,
however, is decided at compile time,
except for variable-size
arrays.
↓↓↓↓↓↓ Stack High memory 지역변수, 매개 변수 Local Varialble,
Parameter
↓↓↓↓↓↓ or ↑↑↑↑↑↑↑ Free Memory
Runtime 결정
A heap is a general term used for any memory
that is allocated dynamically and randomly;
i.e. out of order.
The memory is typically allocated by the OS.
with the application calling API functions
to do this allocation.
There is a fair bit of
overhead required in managing
dynamically allocated memory, which is
usually handled by the runtime code of
the programming language or
environment used.
↑↑↑↑↑↑↑ Heap Low Memory Heap
BSS
초기화 하지 않은
전역, 지역 변수(폐기된)
Uninitialized
discharge and local
variables.
Data
전역변수,정적 변수
Global Variable, Static Variable
Code
실행할 프로그램의 코드
The Code of the program to be executed.
Reserved

Stack buffer overflow[🔝]

stackOverFlowError란?[🔝]

  • 지정한 스택 메모리 사이즈보다 더 많은 스택 메모리를 사용하게 되어 에러가 발생하는 상황을 일컫는다.
    • 즉 스택 포인터가 스택의 경계를 넘어갈때 발생한다.
      • StackOverflowError 발생 종류
        • ① 재귀(Recursive)함수
        • ② 상호 참조
        • ③ 본인 참조
        • https://velog.io/@devnoong/JAVA-Stack-%EA%B3%BC-Heap%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C#outofmemoryerror--java-heap-space-%EB%9E%80

Heap Overflow[🔝]

Stack vs Heap의 장점[🔝]

  • Stack
    • 매우 빠른 액세스
    • 변수를 명시적으로 할당 해제할 필요가 없다
    • 공간은 CPU에 의해 효율적으로 관리되고 메모리는 단편화되지 않는다.
    • 지역 변수만 해당된다
    • 스택 크기 제한(OS에 따라 다르다)
    • 변수의 크기를 조정할 수 없다

  • Heap
    • 변수는 전역적으로 액세스 할 수 있다.
    • 메모리 크기 제한이 없다
    • (상대적으로) 느린 액세스
    • 효율적인 공간 사용을 보장하지 못하면 메모리 블록이 할당된 후 시간이 지남에 따라 메모리가 조각화 되어 해제될 수 있다.
    • 메모리를 관리해야 한다 (변수를 할당하고 해제하는 책임이 있다)
    • 변수는 자바 new를 사용



Rust String VS 다른 언어의 String의 차이점|🔝|

111111

2222222 3333

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

struct

#[derive(Debug)]
struct StoreData {
    name: String,
    age: u8,
    address: String,
}

fn main() {
    let my_student01 = StoreData {
        name: "Gyoung".to_string(),
        age: 40,
        address: "서울".to_string(),
    };

    println!("저장된 데이터 출력 :  {my_student01:?}");
}

Rust_Syntax러스트문법

link


Rust_Getting_Started

Programming_fake_install

Common Programming Concepts

Variables&Mutability

Data_Types데이터 타입

Functions

Comments주석처리

Control Flow(if, else if, loop, while등등)

link


if|🔝|

fn main() {
    // default i32
    let int_a = 10;
    // true or false /   bool
    if int_a > 20 {
        println!("20보다 같거나 작다.")
    } else if int_a > 10 {
    } else {
        println!("10보다 같거나 작다.")
    }
}
  • Result
10보다 같거나 작다.

if & else|🔝|

fn main() {
    let n = 5;
    let check_even_odd = if n % 2 == 0 {
        "짝수even"
    } else {
        "홀수odd"
    };

    println!("홀수인지 짝수인지 확인해 봅시다. : {}", check_even_odd);
}
  • Result
홀수인지 짝수인지 확인해 봅시다. : 홀수odd

while|🔝|

use std::{thread, time::Duration};

fn main() {
    println!("로켓트 카운드 타운 시작: ");

    let mut while_x = 10;
    while while_x != 1 {
        // You can add your logic here
        while_x -= 1; // Decrement the variable to exit the loop
                      // let mut while_x = 10; // Initialize a variable to control the while loop
                      // println!("Inside the while loop");
        thread::sleep(Duration::from_secs(1));
        println!("{} 초", while_x);
    }
    thread::sleep(Duration::from_secs(1));
    println!("0 초 ~~~ 발사 ~~~~")
}
  • Result
로켓트 카운드 타운 시작:
9 초
8 초
7 초
6 초
5 초
4 초
3 초
2 초
1 초
0 초 ~~~ 발사 ~~~~

OwnerShip오너쉽_borrowing

link


러스트 변수value의 3가지 상태|🔝|

  • ① . consume상태(오너쉽을 가지고 있다.)

    • 읽기, 쓰기 , 오너쉽을 넘겨주는 상태(사장님이다. 다 가능)
  • ② . borrowing(& Reference)(빌린 상태라 빚쟁이 같은거다. 사장님이 죽으면 그때 라이프 타임이 끝난다.)

    • 읽기 전용이라 생각하면 된다.
  • ③ . 변수를 바꿀수 있다.(&mut) 변수를 빌려서 (바꿀수 있는)코인 1개를 준다. 1회용 바꿀수 있는 권리가 있다.

    • 바꿀수 있는 기회는 딱 1번뿐..
      • 변수가 바뀐 다음부터는 읽기 전용상태(&)만 가능하다.
  • 예시를 보면서 이해해 보자.

// consume상태-> 오너쉽을 이동했다.
fn owner_consume(x: String) -> String {
    x
}

// borrowing & 읽기 전용
fn owner_reference_pattern(x: &str) -> &str {
    x
}

// &mut 바꾼 다음 읽기전용만 가능
fn mut_str(x: &mut String) -> &mut String {
    x.push_str(" + mutant String");
    x
}

fn main() {
    let x_consume = "consume string".to_string();
    dbg!(owner_consume(x_consume));
    // owner_consume(x_consume.clone()));

    // use of value error
    // dbg!(x_consume);
    // println!("{}", x_consume);

    let x_ref_str = "Reference String";
    dbg!(owner_reference_pattern(x_ref_str));
    dbg!(x_ref_str);

    let mut x_mut_str = "Add String".to_string();
    dbg!(mut_str(&mut x_mut_str));

    let int_x = 40;
    let y = int_x;
    // int는 copy type이라 오너쉽 생각안해도 됨. stack copy됨
    dbg!(int_x);
}
  • Result
[src/main.rs:16:5] owner_consume(x_consume) = "consume string"
[src/main.rs:24:5] owner01(x_ref_str) = "Reference String"
[src/main.rs:25:5] x_ref_str = "Reference String"
[src/main.rs:28:5] mut_str(&mut x_mut_str) = "Add String + mutant String"
[src/main.rs:32:5] int_x = 40

Ownership Rules|🔝|

  • First, let's take a look at the ownership rules. Keep these rules in mind as we work through the examples that illustrate them"

    • Each value in Rust has a variable that's called its owner.
    • There can only be one owner at a time.
    • When the owner goes out of scope, the value will be dropped.
  • 소유권 규칙

    • 먼저, 소유권에 적용되는 규칙부터 살펴보자. 앞으로 살펴볼 예제들은 이 규칙들을 설명하기 위한 것이므로 잘 기억하도록 하자.
      • 러스트가 다루는 각각의 값은 소유자(owner)라고 부르는 변수를 가지고 있다.
      • 특정 시점에 값의 소유자는 단 하나뿐이다.
      • 소유자가 범위를 벗어나면 그 값은 제거된다.

Borrowing rules|🔝|

  • At any given time, you can have either one mutable reference or any number of immutable references.

  • References must always be valid.

Using Structs to Structure Related Data

Struct 정의 및 인스턴스화(Instantiating)

link


tuple struct

#![allow(unused)]
fn main() {
struct TupleStructColor(u8,u8,u8)

struct Datau8(u8)

struct Stringdata(String)
}

unit struct

#![allow(unused)]
fn main() {
struct UnitStruct;
}

User Data

#![allow(unused)]
fn main() {
// {} 괄호 안쪽을 field 필드라고 부른다.
// UserData라는 struct 안에 username, age, email, address, active가 존재한다.
//  추후에 데이터를 부를때 UserData를 통해 접근한다.
struct UserData {
  username: String,
  age: u8,
  email : String,
  address: String,
  active : bool,
}
}

example

#[derive(Debug)]
struct Color(u8,u8,u8);

fn main() {
  let black = Color(0, 0 ,0);
  println!("Black Color : {:?}", black);
}

Enums and Pattern Matching

  • In this chapter, we’ll look at enumerations, also referred to as enums. Enums allow you to define a type by enumerating its possible variants. First we’ll define and use an enum to show how an enum can encode meaning along with data. Next, we’ll explore a particularly useful enum, called Option, which expresses that a value can be either something or nothing. Then we’ll look at how pattern matching in the match expression makes it easy to run different code for different values of an enum. Finally, we’ll cover how the if let construct is another convenient and concise idiom available to handle enums in your code.

    • 이 장에서는 열거형(enum)이라고도 함)을 살펴봅니다. 열거형은 가능한 변형을 열거하여 유형을 정의할 수 있습니다. 먼저 열거형을 정의하고 사용하여 데이터와 함께 의미를 인코딩하는 방법을 보여드리겠습니다. 다음으로 값이 무언가가 될 수도 있고 아무것도 아닐 수도 있음을 표현하는 옵션이라는 특히 유용한 열거형을 살펴봅니다. 그런 다음 일치 표현식의 패턴 매칭을 통해 열거형의 다른 값에 대해 다른 코드를 쉽게 실행할 수 있는 방법을 살펴봅니다. 마지막으로, 코드에서 열거형을 처리할 수 있는 또 다른 편리하고 간결한 관용구가 어떻게 구성되는지 살펴봅니다.
  • Enums Advenced(중급이상내용)

Defining an Enum 정의

  • Where structs give you a way of grouping together related fields and data, like a Rectangle with its width and height, enums give you a way of saying a value is one of a possible set of values. For example, we may want to say that Rectangle is one of a set of possible shapes that also includes Circle and Triangle. To do this, Rust allows us to encode these possibilities as an enum.

    • 구조가 너비와 높이를 가진 직사각형과 같이 관련 필드와 데이터를 그룹화하는 방법을 제공하는 경우, 열거형은 값이 가능한 값 집합 중 하나라고 말할 수 있는 방법을 제공합니다. 예를 들어 직사각형은 원과 삼각형을 포함하는 가능한 도형 집합 중 하나라고 말할 수 있습니다. 이를 위해 Rust를 사용하면 이러한 가능성을 열거형으로 인코딩할 수 있습니다.
  • Let’s look at a situation we might want to express in code and see why enums are useful and more appropriate than structs in this case. Say we need to work with IP addresses. Currently, two major standards are used for IP addresses: version four and version six. Because these are the only possibilities for an IP address that our program will come across, we can enumerate all possible variants, which is where enumeration gets its name.

    • 코드로 표현할 수 있는 상황을 살펴보고 이 경우 에넘이 구조보다 유용하고 더 적절한 이유를 알아보겠습니다. IP 주소로 작업해야 한다고 가정해 보겠습니다. 현재 IP 주소에는 버전 4와 버전 6이라는 두 가지 주요 표준이 사용되고 있습니다. 이는 우리 프로그램이 접할 수 있는 IP 주소의 유일한 가능성이기 때문에 가능한 모든 변형을 열거할 수 있으며, 여기서 열거라는 이름이 붙습니다.
  • Any IP address can be either a version four or a version six address, but not both at the same time. That property of IP addresses makes the enum data structure appropriate because an enum value can only be one of its variants. Both version four and version six addresses are still fundamentally IP addresses, so they should be treated as the same type when the code is handling situations that apply to any kind of IP address.

    • 모든 IP 주소는 버전 4 또는 버전 6 주소가 될 수 있지만 동시에 둘 다 될 수는 없습니다. 이러한 IP 주소의 속성은 열거형 값이 변형 중 하나만 될 수 있기 때문에 열거형 데이터 구조를 적절하게 만듭니다. 버전 4 및 버전 6 주소는 모두 여전히 기본적으로 IP 주소이므로 코드가 모든 종류의 IP 주소에 적용되는 상황을 처리할 때 동일한 유형으로 취급되어야 합니다.
  • We can express this concept in code by defining an IpAddrKind enumeration and listing the possible kinds an IP address can be, V4 and V6. These are the variants of the enum:

    • IpAddrKind 열거형을 정의하고 IP 주소가 될 수 있는 가능한 종류를 나열하여 이 개념을 코드로 표현할 수 있습니다(V4 및 V6). 다음은 열거형의 변형입니다:

위 문장은 러스트 공식 사이트에 있는 내용입니다.

  • 제가 이해한 enum을 설명 드리겠습니다.
enum KeyboardArrowKey {
    ArrowUp,
    ArrowDown,
    ArrowLeft,
    ArrowRight,
}

fn input_arrow(x: KeyboardArrowKey) -> String {
    match x {
        KeyboardArrowKey::ArrowUp => "Up".to_string(),
        KeyboardArrowKey::ArrowDown => "Down".to_string(),
        KeyboardArrowKey::ArrowLeft => "Left".to_string(),
        KeyboardArrowKey::ArrowRight => "Right".to_string(),
    }
}

fn main() {
    let rightkey = KeyboardArrowKey::ArrowRight;
    dbg!(input_arrow(rightkey));
}
  • 보통 enum은 match와 쓰는 경우가 많습니다.

데이터 저장Common Collections


link




물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

Vectors데이터 저장

link


벡터 만들기|🔝|

#![allow(unused)]
fn main() {
let my_vector : Vec<i32> = Vec::new();

// or

let my_vector_macro = vec![];

}

벡터에 데이터 추가하기|🔝|

#![allow(unused)]
fn main() {
let mut my_vector = Vec::new();

my_vector.push(10);
my_vector.push(11);
my_vector.push(12);
my_vector.push(13);
my_vector.push(14);
}

벡터 데이터 읽기|🔝|

#![allow(unused)]
fn main() {
let my_vector = vec![1, 2, 3, 4, 5];

let read_my_vector02 = my_vector[1];

assert_eq![2, read_my_vector02];
}

Matrix로 다차원 구조 만들기|🔝|

2차원 매트릭스(2d matrix)|🔝|

fn main() {
    // 딱딱한 array버젼 변형하기 쉽지 않다.
    let mut state = [[0u8; 4]; 6];
    state[0][1] = 42;
    println!("two dimention : ");
    for matrix_2d in state {
        println!("{matrix_2d:?}");
    }

    // Vector를 활용해서 다양한 데이터를 유연하게 받기 위해 만듬
    println!("\nvector style (two dimention) : ");    
    let mut vector_state = vec![vec![0;4];4];
    vector_state[1][0] = 99;
    for matrix_2d in vector_state {
        println!("{matrix_2d:?}");
    }
} 
  • Result

two dimention : 
[0, 42, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]

vector style (two dimention) : 
[0, 0, 0, 0]
[99, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]

3차원 매트릭스(3d matrix)|🔝|

fn main() {
    let mut state = [[[0u8; 4]; 6];3];
    state[0][1][1] = 42;
    println!("3d dimention : ");
    for matrix_3d in state {
        for matrix_2d in matrix_3d {
        println!("{matrix_2d:?}");    
        }
        println!("~~~dimention line~~~");    
    }

    println!("\n\n~~~\nvector style (3d dimention) : ");    
    let mut vector_state = vec![vec![vec![0;4];6];3];
    vector_state[1][0][0] = 99;
    for matrix_3d in vector_state {
        for matrix_2d in matrix_3d {
        println!("{matrix_2d:?}");    
        }
        println!("~~~dimention line~~~");    
    }
}
  • result
3d dimention : 
[0, 0, 0, 0]
[0, 42, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
~~~dimention line~~~
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
~~~dimention line~~~
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
~~~dimention line~~~


~~~
vector style (3d dimention) : 
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
~~~dimention line~~~
[99, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
~~~dimention line~~~
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
[0, 0, 0, 0]
~~~dimention line~~~

Matrix매트릭스 저장_다차원 구조로 저장


row와 columns 개념은 익히자

image row-major-3D two_dimensional_arrays


link


Matrix매트릭스 저장_다차원 구조로 저장

link


C언어로 2차원 매트릭스(matrix) 만들기

#include <stdio.h>

int main(void) {
    int arr[10];           // 1D array of size 10
    int arr2d[10][10];     // 2D array (matrix) of size 10x10

    // Example: Initializing the 2D array with values
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            arr2d[i][j] = i * 10 + j;  // Assigning some values (e.g., row-major order)
        }
    }

    // 0 으로 초기화하기
    // for (int i = 0; i < 10; i++) {
    //     for (int j = 0; j < 10; j++) {
    //         arr2d[i][j] = 0;
    //     }
    // }

    // Example: Printing the 2D array
    printf("2D Matrix:\n");
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            printf("%3d ", arr2d[i][j]);  // Printing each element
        }
        printf("\n");
    }

    return 0;
}
  • result
2D Matrix:
  0   1   2   3   4   5   6   7   8   9
 10  11  12  13  14  15  16  17  18  19
 20  21  22  23  24  25  26  27  28  29
 30  31  32  33  34  35  36  37  38  39
 40  41  42  43  44  45  46  47  48  49
 50  51  52  53  54  55  56  57  58  59
 60  61  62  63  64  65  66  67  68  69
 70  71  72  73  74  75  76  77  78  79
 80  81  82  83  84  85  86  87  88  89
 90  91  92  93  94  95  96  97  98  99

Rust 고급 기술

traits

link


가장 어려운 traits다.

  • traits는 누구나 어렵다. 유일한 방법은 수많은 예시를 해보고, 울면서 cargo에 거부 당하면서, 고통 받다보면, 알아서 실력이 향상되는 유일한 방법이다. 무식하지만 확실하다.

  • 지금까지 상단에 아무 생각없이 쓰던 derive의 정체는 trait다. 기본적인 트레이트는 암기하도록하자.

    • Debug, Default, PartialEq, Eq 등등
#[derive(Debug)]

  • 다음 장은 D&D던전앤 드래곤을 예시를 하면서 감각을 익히도록하겠다.
    • game만드는 기분으로 코딩하는게 개인적으로 빨리 실력이 느는것 같다. 재밌고. ㅋ

던전앤드래곤 예시(Trait)

  • trait는 doing 행동만 만들어준다.
struct Dwarf {
    name: String,
}

struct Elf {
    name: String,
}

struct HalfOrc {
    name: String,
}

struct HalfElf {
    name: String,
}

struct Human {
    name: String,
}

impl Constitution for Dwarf {
    fn constitution_bonus(&self) -> u8 {
        2
    }
}

impl Constitution for HalfOrc {
    fn constitution_bonus(&self) -> u8 {
        1
    }
}

impl Constitution for Elf {}

impl Constitution for Human {}

pub trait Constitution {
    fn constitution_bonus(&self) -> u8 {
        0
    }
}

pub trait Elvish {
    fn speak_elvish(&self) -> String {
        String::from("yes")
    }

    fn no_speak_elvish<T>(&self) -> String {
        String::from("no")
    }
}

impl Elvish for Elf {}
impl Elvish for HalfElf {}
impl Elvish for HalfOrc {}

fn main() {
    let my_dwaft = Dwarf {
        name: String::from("NellDwaft"),
    };

    let my_elf = Elf {
        name: String::from("NellElf"),
    };

    let my_half_elf = HalfElf {
        name: String::from("NellElf"),
    };

    let my_half_orc = HalfOrc {
        name: String::from("NellHalfOrc"),
    };

    let my_human = Human {
        name: String::from("NellHuman"),
    };

    // Return 2
    dbg!(my_dwaft.constitution_bonus());

    // Return 1
    dbg!(my_half_orc.constitution_bonus());

    // Return 0
    dbg!(my_elf.constitution_bonus());
    dbg!(my_human.constitution_bonus());

    // Return "yes"
    dbg!(my_elf.speak_elvish());
    dbg!(my_half_elf.speak_elvish());

    // Return "no"
    dbg!(my_half_orc.speak_elvish());
}

  • Result
[src\main.rs:88] my_dwaft.constitution_bonus() = 2
[src\main.rs:91] my_half_orc.constitution_bonus() = 1
[src\main.rs:94] my_elf.constitution_bonus() = 0
[src\main.rs:95] my_human.constitution_bonus() = 0
[src\main.rs:98] my_elf.speak_elvish() = "yes"
[src\main.rs:99] my_half_elf.speak_elvish() = "yes"
[src\main.rs:102] my_half_orc.speak_elvish() = "yes"

Sizedness-in-rust

Sizedness in Rust

  • 22 July 2020 · #rust · #sizedness

link

Table of Contents


Intro|🔝|

  • Sizedness is lowkey one of the most important concepts to understand in Rust. It intersects a bunch of other language features in often subtle ways and only rears its ugly head in the form of "x doesn't have size known at compile time" error messages which every Rustacean is all too familiar with. In this article we'll explore all flavors of sizedness from sized types, to unsized types, to zero-sized types while examining their use-cases, benefits, pain points, and workarounds.

    • 크기는 러스트에서 이해해야 할 가장 중요한 개념 중 하나입니다. 이 개념은 종종 미묘한 방식으로 다른 언어 기능을 많이 교차하며, 모든 러스트족이 너무 익숙한 "x는 컴파일 시점에 크기를 알 수 없습니다" 오류 메시지의 형태로만 추악한 머리를 들려줍니다. 이 글에서는 크기 유형부터 크기가 없는 유형, 크기가 없는 유형, 크기가 없는 유형에 이르기까지 모든 종류의 크기를 탐색하는 동시에 사용 사례, 이점, 문제점 및 해결 방법을 살펴봅니다.
  • Table of phrases I use and what they're supposed to mean:

    • 제가 사용하는 문구 표와 그 의미:
PhraseShorthand for
sizednessproperty of being sized or unsized
sized typetype with a known size at compile time
1) unsized type or
2) DST
dynamically-sized type, i.e. size not known at compile time
?sized typetype that may or may not be sized
unsized coercioncoercing a sized type into an unsized type
ZSTzero-sized type, i.e. instances of the type are 0 bytes in size
widthsingle unit of measurement of pointer width
1) thin pointer or
2) single-width pointer
pointer that is 1 width
1) fat pointer or
2) double-width pointer
pointer that is 2 widths
1) pointer or
2) reference
some pointer of some width, width will be clarified by context
slicedouble-width pointer to a dynamically sized view into some array

Sizedness|🔝|

  • In Rust a type is sized if its size in bytes can be determined at compile-time. Determining a type's size is important for being able to allocate enough space for instances of that type on the stack. Sized types can be passed around by value or by reference. If a type's size can't be determined at compile-time then it's referred to as an unsized type or a DST, Dynamically-Sized Type. Since unsized types can't be placed on the stack they can only be passed around by reference. Some examples of sized and unsized types:
    • Rust에서 유형의 크기(바이트)를 컴파일 시간에 결정할 수 있는지 여부는 크기가 결정됩니다. 유형의 크기를 결정하는 것은 스택에서 해당 유형의 인스턴스에 충분한 공간을 할당할 수 있으려면 중요합니다. 크기가 있는 유형은 값별로 또는 참조로 전달할 수 있습니다. 컴파일 시간에 유형의 크기를 결정할 수 없는 경우 이를 크기가 없는 유형 또는 동적 크기가 있는 DST 유형이라고 합니다. 크기가 없는 유형은 스택에 배치할 수 없으므로 참조로만 전달할 수 있습니다. 크기가 큰 유형과 크기가 없는 유형의 몇 가지 예입니다:
use std::mem::size_of;

fn main() {
    // primitives
    assert_eq!(4, size_of::<i32>());
    assert_eq!(8, size_of::<f64>());

    // tuples
    assert_eq!(8, size_of::<(i32, i32)>());

    // arrays
    assert_eq!(0, size_of::<[i32; 0]>());
    assert_eq!(12, size_of::<[i32; 3]>());

    struct Point {
        x: i32,
        y: i32,
    }

    // structs
    assert_eq!(8, size_of::<Point>());

    // enums
    assert_eq!(8, size_of::<Option<i32>>());

    // get pointer width, will be
    // 4 bytes wide on 32-bit targets or
    // 8 bytes wide on 64-bit targets
    const WIDTH: usize = size_of::<&()>();

    // pointers to sized types are 1 width
    assert_eq!(WIDTH, size_of::<&i32>());
    assert_eq!(WIDTH, size_of::<&mut i32>());
    assert_eq!(WIDTH, size_of::<Box<i32>>());
    assert_eq!(WIDTH, size_of::<fn(i32) -> i32>());

    const DOUBLE_WIDTH: usize = 2 * WIDTH;

    // unsized struct
    struct Unsized {
        unsized_field: [i32],
    }

    // pointers to unsized types are 2 widths
    assert_eq!(DOUBLE_WIDTH, size_of::<&str>()); // slice
    assert_eq!(DOUBLE_WIDTH, size_of::<&[i32]>()); // slice
    assert_eq!(DOUBLE_WIDTH, size_of::<&dyn ToString>()); // trait object
    assert_eq!(DOUBLE_WIDTH, size_of::<Box<dyn ToString>>()); // trait object
    assert_eq!(DOUBLE_WIDTH, size_of::<&Unsized>()); // user-defined unsized type

    // unsized types
    size_of::<str>(); // compile error
    size_of::<[i32]>(); // compile error
    size_of::<dyn ToString>(); // compile error
    size_of::<Unsized>(); // compile error
}
  • How we determine the size of sized types is straight-forward: all primitives and pointers have known sizes and all structs, tuples, enums, and arrays are just made up of primitives and pointers or other nested structs, tuples, enums, and arrays so we can just count up the bytes recursively, taking into account extra bytes needed for padding and alignment. We can't determine the size of unsized types for similarly straight-forward reasons: slices can have any number of elements in them and can thus be of any size at run-time and trait objects can be implemented by any number of structs or enums and thus can also be of any size at run-time.
    • 크기가 큰 유형의 크기를 결정하는 방법은 간단합니다. 모든 프리미티브와 포인터에는 크기가 알려져 있고 모든 구조, 튜플, 에넘, 어레이는 프리미티브와 포인터 또는 기타 중첩된 구조, 튜플, 에넘, 어레이로 구성되어 패딩과 정렬에 필요한 추가 바이트를 고려하여 바이트를 재귀적으로 카운트할 수 있습니다. 슬라이스에는 여러 개의 요소가 포함될 수 있으므로 런타임에 모든 크기가 될 수 있고 특성 개체는 여러 개의 구조 또는 에넘으로 구현될 수 있으므로 런타임에 모든 크기가 될 수도 있습니다.

Pro tips

  • pointers of dynamically sized views into arrays are called slices in Rust, e.g. a &str is a "string slice", a &[i32] is an "i32 slice"
  • slices are double-width because they store a pointer to the array and the number of elements in the array
  • trait object pointers are double-width because they store a pointer to the data and a pointer to a vtable
  • unsized structs pointers are double-width because they store a pointer to the struct data and the size of the struct
  • unsized structs can only have 1 unsized field and it must be the last field in the struct

To really hammer home the point about double-width pointers for unsized types here's a commented code example comparing arrays to slices:

  • 동적 크기의 뷰를 배열에 넣는 포인터를 러스트에서 슬라이스라고 합니다. 예를 들어, '&str'은 "끈 슬라이스", '&[i32]'는 "i32 슬라이스"입니다
  • 슬라이스는 배열에 대한 포인터와 배열의 요소 수를 저장하기 때문에 두 배 너비입니다
  • 특성 객체 포인터는 데이터에 대한 포인터와 vtable에 대한 포인터를 저장하기 때문에 두 배 폭입니다
  • 크기가 작은 구조물 포인터는 구조물 데이터와 구조물의 크기에 대한 포인터를 저장하기 때문에 두 배 너비입니다
  • 크기가 작은 구조물에는 크기가 작은 필드가 하나만 있을 수 있으며 구조물의 마지막 필드여야 합니다

크기가 크지 않은 유형의 두 배 너비 포인터에 대한 요점을 파악하기 위해 배열과 슬라이스를 비교한 코드 예제를 소개합니다:

use std::mem::size_of;

const WIDTH: usize = size_of::<&()>();
const DOUBLE_WIDTH: usize = 2 * WIDTH;

fn main() {
    // data length stored in type
    // an [i32; 3] is an array of three i32s
    let nums: &[i32; 3] = &[1, 2, 3];

    // single-width pointer
    assert_eq!(WIDTH, size_of::<&[i32; 3]>());

    let mut sum = 0;

    // can iterate over nums safely
    // Rust knows it's exactly 3 elements
    for num in nums {
        sum += num;
    }

    assert_eq!(6, sum);

    // unsized coercion from [i32; 3] to [i32]
    // data length now stored in pointer
    let nums: &[i32] = &[1, 2, 3];

    // double-width pointer required to also store data length
    assert_eq!(DOUBLE_WIDTH, size_of::<&[i32]>());

    let mut sum = 0;

    // can iterate over nums safely
    // Rust knows it's exactly 3 elements
    for num in nums {
        sum += num;
    }

    assert_eq!(6, sum);
}

And here's another commented code example comparing structs to trait objects:

use std::mem::size_of;

const WIDTH: usize = size_of::<&()>();
const DOUBLE_WIDTH: usize = 2 * WIDTH;

trait Trait {
    fn print(&self);
}

struct Struct;
struct Struct2;

impl Trait for Struct {
    fn print(&self) {
        println!("struct");
    }
}

impl Trait for Struct2 {
    fn print(&self) {
        println!("struct2");
    }
}

fn print_struct(s: &Struct) {
    // always prints "struct"
    // this is known at compile-time
    s.print();
    // single-width pointer
    assert_eq!(WIDTH, size_of::<&Struct>());
}

fn print_struct2(s2: &Struct2) {
    // always prints "struct2"
    // this is known at compile-time
    s2.print();
    // single-width pointer
    assert_eq!(WIDTH, size_of::<&Struct2>());
}

fn print_trait(t: &dyn Trait) {
    // print "struct" or "struct2" ?
    // this is unknown at compile-time
    t.print();
    // Rust has to check the pointer at run-time
    // to figure out whether to use Struct's
    // or Struct2's implementation of "print"
    // so the pointer has to be double-width
    assert_eq!(DOUBLE_WIDTH, size_of::<&dyn Trait>());
}

fn main() {
    // single-width pointer to data
    let s = &Struct; 
    print_struct(s); // prints "struct"
    
    // single-width pointer to data
    let s2 = &Struct2;
    print_struct2(s2); // prints "struct2"
    
    // unsized coercion from Struct to dyn Trait
    // double-width pointer to point to data AND Struct's vtable
    let t: &dyn Trait = &Struct;
    print_trait(t); // prints "struct"
    
    // unsized coercion from Struct2 to dyn Trait
    // double-width pointer to point to data AND Struct2's vtable
    let t: &dyn Trait = &Struct2;
    print_trait(t); // prints "struct2"
}

Key Takeaways

  • only instances of sized types can be placed on the stack, i.e. can be passed around by value
  • instances of unsized types can't be placed on the stack and must be passed around by reference
  • pointers to unsized types are double-width because aside from pointing to data they need to do an extra bit of bookkeeping to also keep track of the data's length or point to a vtable
  • 크기가 큰 유형의 인스턴스만 스택에 배치할 수 있습니다
  • 크기가 작은 유형의 인스턴스는 스택에 배치할 수 없으며 참조하여 전달해야 합니다
  • 크기가 작은 유형의 포인터는 두 배 너비이므로 데이터를 가리키는 것 외에도 데이터의 길이를 추적하기 위해 추가로 약간의 부기를 해야 합니다(또는 테이블을 가리키는 것)

Sized Trait|🔝|

The Sized trait in Rust is an auto trait and a marker trait.

Auto traits are traits that get automatically implemented for a type if it passes certain conditions. Marker traits are traits that mark a type as having a certain property. Marker traits do not have any trait items such as methods, associated functions, associated constants, or associated types. All auto traits are marker traits but not all marker traits are auto traits. Auto traits must be marker traits so the compiler can provide an automatic default implementation for them, which would not be possible if the trait had any trait items.

A type gets an auto Sized implementation if all of its members are also Sized. What "members" means depends on the containing type, for example: fields of a struct, variants of an enum, elements of an array, items of a tuple, and so on. Once a type has been "marked" with a Sized implementation that means its size in bytes is known at compile time.

Other examples of auto marker traits are the Send and Sync traits. A type is Send if it is safe to send that type across threads. A type is Sync if it's safe to share references of that type between threads. A type gets auto Send and Sync implementations if all of its members are also Send and Sync. What makes Sized somewhat special is that it's not possible to opt-out of unlike with the other auto marker traits which are possible to opt-out of.

  • 러스트의 '사이즈' 특성은 자동 특성과 마커 특성입니다.

자동 특성은 특정 조건을 통과하면 유형에 대해 자동으로 구현되는 특성입니다. 마커 특성은 유형이 특정 속성을 가진 것으로 표시되는 특성입니다. 마커 특성에는 방법, 관련 함수, 관련 상수 또는 관련 유형과 같은 특성 항목이 없습니다. 모든 자동 특성은 마커 특성이지만 모든 마커 특성이 자동 특성인 것은 아닙니다. 자동 특성은 마커 특성이어야 컴파일러가 자동 기본 구현을 제공할 수 있으며, 특성에 특성 항목이 있는 경우 불가능합니다.

모든 구성원이 '크기'인 경우 유형은 자동 '크기' 구현을 얻습니다. 예를 들어 "멤버"가 의미하는 것은 포함된 유형에 따라 달라집니다. 유형이 '크기' 구현으로 '표시'되면 컴파일 시 바이트 단위로 해당 크기를 알 수 있습니다.

자동 마커 특성의 다른 예로는 '보내기' 및 '동기화' 특성이 있습니다. 한 유형은 스레드 간에 해당 유형을 전송하는 것이 안전한 경우 '보내기'입니다. 한 유형은 스레드 간에 해당 유형의 참조를 공유하는 것이 안전한 경우 '동기화'입니다. 모든 구성원이 '보내기' 및 '동기화'인 경우 자동으로 '보내기' 및 '동기화'를 구현할 수 있습니다. '크기'를 다소 특별하게 만드는 것은 옵트아웃할 수 있는 다른 자동 마커 특성과 달리 옵트아웃할 수 없다는 점입니다.

#![allow(unused)]
#![feature(negative_impls)]

fn main() {
// this type is Sized, Send, and Sync
struct Struct;

// opt-out of Send trait
impl !Send for Struct {} // ✅

// opt-out of Sync trait
impl !Sync for Struct {} // ✅

// can't opt-out of Sized
impl !Sized for Struct {} // ❌
}

This seems reasonable since there might be reasons why we wouldn't want our type to be sent or shared across threads, however it's hard to imagine a scenario where we'd want the compiler to "forget" the size of our type and treat it as an unsized type as that offers no benefits and merely makes the type more difficult to work with.

Also, to be super pedantic Sized is not technically an auto trait since it's not defined using the auto keyword but the special treatment it gets from the compiler makes it behave very similarly to auto traits so in practice it's okay to think of it as an auto trait.

  • 스레드 간에 우리 유형을 전송하거나 공유하는 것을 원하지 않는 이유가 있을 수 있지만, 컴파일러가 우리 유형의 크기를 '잊고' 크기가 없는 유형으로 취급하여 혜택을 제공하지 않고 유형을 작업하기 어렵게 만드는 시나리오를 상상하기는 어렵습니다.

또한 초현학적인 '사이즈'는 '자동' 키워드로 정의되지 않았기 때문에 엄밀히 말하면 자동 특성이 아니지만 컴파일러로부터 받는 특별한 대우로 인해 자동 특성과 매우 유사하게 행동하므로 실제로는 자동 특성으로 생각해도 괜찮습니다.

Key Takeaways

  • Sized is an "auto" marker trait

Sized in Generics|🔝|

It's not immediately obvious that whenever we write any generic code every generic type parameter gets auto-bound with the Sized trait by default.

  • 일반 코드를 작성할 때마다 모든 일반 유형 매개 변수가 기본적으로 '크기' 특성으로 자동 바인딩된다는 것이 즉시 명확하지는 않습니다.
#![allow(unused)]
fn main() {
// this generic function...
fn func<T>(t: T) {}

// ...desugars to...
fn func<T: Sized>(t: T) {}

// ...which we can opt-out of by explicitly setting ?Sized...
fn func<T: ?Sized>(t: T) {} // ❌

// ...which doesn't compile since it doesn't have
// a known size so we must put it behind a pointer...
fn func<T: ?Sized>(t: &T) {} // ✅
fn func<T: ?Sized>(t: Box<T>) {} // ✅
}

Pro tips

  • ?Sized can be pronounced "optionally sized" or "maybe sized" and adding it to a type parameter's bounds allows the type to be sized or unsized
  • ?Sized in general is referred to as a "widening bound" or a "relaxed bound" as it relaxes rather than constrains the type parameter
  • ?Sized is the only relaxed bound in Rust

So why does this matter? Well, any time we're working with a generic type and that type is behind a pointer we almost always want to opt-out of the default Sized bound to make our function more flexible in what argument types it will accept. Also, if we don't opt-out of the default Sized bound we'll eventually get some surprising and confusing compile error messages.

Let me take you on the journey of the first generic function I ever wrote in Rust. I started learning Rust before the dbg! macro landed in stable so the only way to print debug values was to type out println!("{:?}", some_value); every time which is pretty tedious so I decided to write a debug helper function like this:

use std::fmt::Debug;

fn debug<T: Debug>(t: T) { // T: Debug + Sized
    println!("{:?}", t);
}

fn main() {
    debug("my str"); // T = &str, &str: Debug + Sized ✅
}

So far so good, but the function takes ownership of any values passed to it which is kinda annoying so I changed the function to only take references instead:

use std::fmt::Debug;

fn dbg<T: Debug>(t: &T) { // T: Debug + Sized
    println!("{:?}", t);
}

fn main() {
    dbg("my str"); // &T = &str, T = str, str: Debug + !Sized ❌
}

Which now throws this error:

error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> src/main.rs:8:9
  |
3 | fn dbg<T: Debug>(t: &T) {
  |        - required by this bound in `dbg`
...
8 |     dbg("my str");
  |         ^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `str`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
help: consider relaxing the implicit `Sized` restriction
  |
3 | fn dbg<T: Debug + ?Sized>(t: &T) {
  |   

When I first saw this I found it incredibly confusing. Despite making my function more restrictive in what arguments it takes than before it now somehow throws a compile error! What is going on?

I've already kinda spoiled the answer in the code comments above, but basically: Rust performs pattern matching when resolving T to its concrete types during compilation. Here's a couple tables to help clarify:

TypeT&T
&strT = &strT = str
TypeSized
str
&str
&&str

This is why I had to add a ?Sized bound to make the function work as intended after changing it to take references. The working function below:

use std::fmt::Debug;

fn debug<T: Debug + ?Sized>(t: &T) { // T: Debug + ?Sized
    println!("{:?}", t);
}

fn main() {
    debug("my str"); // &T = &str, T = str, str: Debug + !Sized ✅
}

Key Takeaways

  • all generic type parameters are auto-bound with Sized by default
  • if we have a generic function which takes an argument of some T behind a pointer, e.g. &T, Box<T>, Rc<T>, et cetera, then we almost always want to opt-out of the default Sized bound with T: ?Sized


Unsized Types|🔝|

Slices|🔝|

The most common slices are string slices &str and array slices &[T]. What's nice about slices is that many other types coerce to them, so leveraging slices and Rust's auto type coercions allow us to write flexible APIs.

Type coercions can happen in several places but most notably on function arguments and at method calls. The kinds of type coercions we're interested in are deref coercions and unsized coercions. A deref coercion is when a T gets coerced into a U following a deref operation, i.e. T: Deref<Target = U>, e.g. String.deref() -> str. An unsized coercion is when a T gets coerced into a U where T is a sized type and U is an unsized type, i.e. T: Unsize<U>, e.g. [i32; 3] -> [i32].

trait Trait {
    fn method(&self) {}
}

impl Trait for str {
    // can now call "method" on
    // 1) str or
    // 2) String since String: Deref<Target = str>
}
impl<T> Trait for [T] {
    // can now call "method" on
    // 1) any &[T]
    // 2) any U where U: Deref<Target = [T]>, e.g. Vec<T>
    // 3) [T; N] for any N, since [T; N]: Unsize<[T]>
}

fn str_fun(s: &str) {}
fn slice_fun<T>(s: &[T]) {}

fn main() {
    let str_slice: &str = "str slice";
    let string: String = "string".to_owned();

    // function args
    str_fun(str_slice);
    str_fun(&string); // deref coercion

    // method calls
    str_slice.method();
    string.method(); // deref coercion

    let slice: &[i32] = &[1];
    let three_array: [i32; 3] = [1, 2, 3];
    let five_array: [i32; 5] = [1, 2, 3, 4, 5];
    let vec: Vec<i32> = vec![1];

    // function args
    slice_fun(slice);
    slice_fun(&vec); // deref coercion
    slice_fun(&three_array); // unsized coercion
    slice_fun(&five_array); // unsized coercion

    // method calls
    slice.method();
    vec.method(); // deref coercion
    three_array.method(); // unsized coercion
    five_array.method(); // unsized coercion
}

Key Takeaways

  • leveraging slices and Rust's auto type coercions allows us to write flexible APIs

Trait Objects|🔝|

Traits are ?Sized by default. This program:

#![allow(unused)]
fn main() {
trait Trait: ?Sized {}
}

Throws this error:

error: `?Trait` is not permitted in supertraits
 --> src/main.rs:1:14
  |
1 | trait Trait: ?Sized {}
  |              ^^^^^^
  |
  = note: traits are `?Sized` by default

We'll get into why traits are ?Sized by default soon but first let's ask ourselves what are the implications of a trait being ?Sized? Let's desugar the above example:

#![allow(unused)]
fn main() {
trait Trait where Self: ?Sized {}
}

Okay, so by default traits allow self to possibly be an unsized type. As we learned earlier we can't pass unsized types around by value, so that limits us in the kind of methods we can define in the trait. It should be impossible to write a method the takes or returns self by value and yet this surprisingly compiles:

#![allow(unused)]
fn main() {
trait Trait {
    fn method(self); // ✅
}
}

However the moment we try to implement the method, either by providing a default implementation or by implementing the trait for an unsized type, we get compile errors:

#![allow(unused)]
fn main() {
trait Trait {
    fn method(self) {} // ❌
}

impl Trait for str {
    fn method(self) {} // ❌
}
}

Throws:

error[E0277]: the size for values of type `Self` cannot be known at compilation time
 --> src/lib.rs:2:15
  |
2 |     fn method(self) {}
  |               ^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `Self`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: all local variables must have a statically known size
  = help: unsized locals are gated as an unstable feature
help: consider further restricting `Self`
  |
2 |     fn method(self) where Self: std::marker::Sized {}
  |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> src/lib.rs:6:15
  |
6 |     fn method(self) {}
  |               ^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `str`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: all local variables must have a statically known size
  = help: unsized locals are gated as an unstable feature

If we're determined to pass self around by value we can fix the first error by explicitly binding the trait with Sized:

#![allow(unused)]
fn main() {
trait Trait: Sized {
    fn method(self) {} // ✅
}

impl Trait for str { // ❌
    fn method(self) {}
}
}

Now throws:

error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> src/lib.rs:7:6
  |
1 | trait Trait: Sized {
  |              ----- required by this bound in `Trait`
...
7 | impl Trait for str {
  |      ^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `str`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>

Which is okay, as we knew upon binding the trait with Sized we'd no longer be able to implement it for unsized types such as str. If on the other hand we really wanted to implement the trait for str an alternative solution would be to keep the trait ?Sized and pass self around by reference:

#![allow(unused)]
fn main() {
trait Trait {
    fn method(&self) {} // ✅
}

impl Trait for str {
    fn method(&self) {} // ✅
}
}

Instead of marking the entire trait as ?Sized or Sized we have the more granular and precise option of marking individual methods as Sized like so:

trait Trait {
    fn method(self) where Self: Sized {}
}

impl Trait for str {} // ✅!?

fn main() {
    "str".method(); // ❌
}

It's surprising that Rust compiles impl Trait for str {} without any complaints, but it eventually catches the error when we attempt to call method on an unsized type so all is fine. It's a little weird but affords us some flexibility in implementing traits with some Sized methods for unsized types as long as we never call the Sized methods:

trait Trait {
    fn method(self) where Self: Sized {}
    fn method2(&self) {}
}

impl Trait for str {} // ✅

fn main() {
    // we never call "method" so no errors
    "str".method2(); // ✅
}

Now back to the original question, why are traits ?Sized by default? The answer is trait objects. Trait objects are inherently unsized because any type of any size can implement a trait, therefore we can only implement Trait for dyn Trait if Trait: ?Sized. To put it in code:

#![allow(unused)]
fn main() {
trait Trait: ?Sized {}

// the above is REQUIRED for

impl Trait for dyn Trait {
    // compiler magic here
}

// since `dyn Trait` is unsized

// and now we can use `dyn Trait` in our program

fn function(t: &dyn Trait) {} // ✅
}

If we try to actually compile the above program we get:

error[E0371]: the object type `(dyn Trait + 'static)` automatically implements the trait `Trait`
 --> src/lib.rs:5:1
  |
5 | impl Trait for dyn Trait {
  | ^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Trait + 'static)` automatically implements trait `Trait`

Which is the compiler telling us to chill since it automatically provides the implementation of Trait for dyn Trait. Again, since dyn Trait is unsized the compiler can only provide this implementation if Trait: ?Sized. If we bound Trait by Sized then Trait becomes "object unsafe" which is a term that means we can't cast types which implement Trait to trait objects of dyn Trait. As expected this program does not compile:

#![allow(unused)]
fn main() {
trait Trait: Sized {}

fn function(t: &dyn Trait) {} // ❌
}

Throws:

error[E0038]: the trait `Trait` cannot be made into an object
 --> src/lib.rs:3:18
  |
1 | trait Trait: Sized {}
  |       -----  ----- ...because it requires `Self: Sized`
  |       |
  |       this trait cannot be made into an object...
2 | 
3 | fn function(t: &dyn Trait) {}
  |                ^^^^^^^^^^ the trait `Trait` cannot be made into an object

Let's try to make an ?Sized trait with a Sized method and see if we can cast it to a trait object:

#![allow(unused)]
fn main() {
trait Trait {
    fn method(self) where Self: Sized {}
    fn method2(&self) {}
}

fn function(arg: &dyn Trait) { // ✅
    arg.method(); // ❌
    arg.method2(); // ✅
}
}

As we saw before everything is okay as long as we don't call the Sized method on the trait object.

Key Takeaways

  • all traits are ?Sized by default
  • Trait: ?Sized is required for impl Trait for dyn Trait
  • we can require Self: Sized on a per-method basis
  • traits bound by Sized can't be made into trait objects

Trait Object Limitations

Even if a trait is object-safe there are still sizedness-related edge cases which limit what types can be cast to trait objects and how many and what kind of traits can be represented by a trait object.

Cannot Cast Unsized Types to Trait Objects|🔝|

fn generic<T: ToString>(t: T) {}
fn trait_object(t: &dyn ToString) {}

fn main() {
    generic(String::from("String")); // ✅
    generic("str"); // ✅
    trait_object(&String::from("String")); // ✅ - unsized coercion
    trait_object("str"); // ❌ - unsized coercion impossible
}

Throws:

error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> src/main.rs:8:18
  |
8 |     trait_object("str");
  |                  ^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `str`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: required for the cast to the object type `dyn std::string::ToString`

The reason why passing a &String to a function expecting a &dyn ToString works is because of type coercion. String implements ToString and we can convert a sized type such as String into an unsized type such as dyn ToString via an unsized coercion. str also implements ToString and converting str into a dyn ToString would also require an unsized coercion but str is already unsized! How do we unsize an already unsized type into another unsized type?

&str pointers are double-width, storing a pointer to the data and the data length. &dyn ToString pointers are also double-width, storing a pointer to the data and a pointer to a vtable. To coerce a &str into a &dyn toString would require a triple-width pointer to store a pointer to the data, the data length, and a pointer to a vtable. Rust does not support triple-width pointers so casting an unsized type to a trait object is not possible.

Previous two paragraphs summarized in a table:

TypePointer to DataData LengthPointer to VTableTotal Width
&String1 ✅
&str2 ✅
&String as &dyn ToString2 ✅
&str as &dyn ToString3 ❌

Cannot create Multi-Trait Objects|🔝|

#![allow(unused)]
fn main() {
trait Trait {}
trait Trait2 {}

fn function(t: &(dyn Trait + Trait2)) {}
}

Throws:

error[E0225]: only auto traits can be used as additional traits in a trait object
 --> src/lib.rs:4:30
  |
4 | fn function(t: &(dyn Trait + Trait2)) {}
  |                      -----   ^^^^^^
  |                      |       |
  |                      |       additional non-auto trait
  |                      |       trait alias used in trait object type (additional use)
  |                      first non-auto trait
  |                      trait alias used in trait object type (first use)

Remember that a trait object pointer is double-width: storing 1 pointer to the data and another to the vtable, but there's 2 traits here so there's 2 vtables which would require the &(dyn Trait + Trait2) pointer to be 3 widths. Auto-traits like Sync and Send are allowed since they don't have methods and thus don't have vtables.

The workaround for this is to combine vtables by combining the traits using another trait like so:

#![allow(unused)]
fn main() {
trait Trait {
    fn method(&self) {}
}

trait Trait2 {
    fn method2(&self) {}
}

trait Trait3: Trait + Trait2 {}

// auto blanket impl Trait3 for any type that also impls Trait & Trait2
impl<T: Trait + Trait2> Trait3 for T {}

// from `dyn Trait + Trait2` to `dyn Trait3` 
fn function(t: &dyn Trait3) {
    t.method(); // ✅
    t.method2(); // ✅
}
}

One downside of this workaround is that Rust does not support supertrait upcasting. What this means is that if we have a dyn Trait3 we can't use it where we need a dyn Trait or a dyn Trait2. This program does not compile:

trait Trait {
    fn method(&self) {}
}

trait Trait2 {
    fn method2(&self) {}
}

trait Trait3: Trait + Trait2 {}

impl<T: Trait + Trait2> Trait3 for T {}

struct Struct;
impl Trait for Struct {}
impl Trait2 for Struct {}

fn takes_trait(t: &dyn Trait) {}
fn takes_trait2(t: &dyn Trait2) {}

fn main() {
    let t: &dyn Trait3 = &Struct;
    takes_trait(t); // ❌
    takes_trait2(t); // ❌
}

Throws:

error[E0308]: mismatched types
  --> src/main.rs:22:17
   |
22 |     takes_trait(t);
   |                 ^ expected trait `Trait`, found trait `Trait3`
   |
   = note: expected reference `&dyn Trait`
              found reference `&dyn Trait3`

error[E0308]: mismatched types
  --> src/main.rs:23:18
   |
23 |     takes_trait2(t);
   |                  ^ expected trait `Trait2`, found trait `Trait3`
   |
   = note: expected reference `&dyn Trait2`
              found reference `&dyn Trait3`

This is because dyn Trait3 is a distinct type from dyn Trait and dyn Trait2 in the sense that they have different vtable layouts, although dyn Trait3 does contain all the methods of dyn Trait and dyn Trait2. The workaround here is to add explicit casting methods:

trait Trait {}
trait Trait2 {}

trait Trait3: Trait + Trait2 {
    fn as_trait(&self) -> &dyn Trait;
    fn as_trait2(&self) -> &dyn Trait2;
}

impl<T: Trait + Trait2> Trait3 for T {
    fn as_trait(&self) -> &dyn Trait {
        self
    }
    fn as_trait2(&self) -> &dyn Trait2 {
        self
    }
}

struct Struct;
impl Trait for Struct {}
impl Trait2 for Struct {}

fn takes_trait(t: &dyn Trait) {}
fn takes_trait2(t: &dyn Trait2) {}

fn main() {
    let t: &dyn Trait3 = &Struct;
    takes_trait(t.as_trait()); // ✅
    takes_trait2(t.as_trait2()); // ✅
}

This is a simple and straight-forward workaround that seems like something the Rust compiler could automate for us. Rust is not shy about performing type coercions as we have seen with deref and unsized coercions, so why isn't there a trait upcasting coercion? This is a good question with a familiar answer: the Rust core team is working on other higher-priority and higher-impact features. Fair enough.

Key Takeaways

  • Rust doesn't support pointers wider than 2 widths so
    • we can't cast unsized types to trait objects
    • we can't have multi-trait objects, but we can work around this by coalescing multiple traits into a single trait

User-Defined Unsized Types|🔝|

#![allow(unused)]
fn main() {
struct Unsized {
    unsized_field: [i32],
}
}

We can define an unsized struct by giving the struct an unsized field. Unsized structs can only have 1 unsized field and it must be the last field in the struct. This is a requirement so that the compiler can determine the starting offset of every field in the struct at compile-time, which is important for efficient and fast field access. Furthermore, a single unsized field is the most that can be tracked using a double-width pointer, as more unsized fields would require more widths.

So how do we even instantiate this thing? The same way we do with any unsized type: by first making a sized version of it then coercing it into the unsized version. However, Unsized is always unsized by definition, there's no way to make a sized version of it! The only workaround is to make the struct generic so that it can exist in both sized and unsized versions:

struct MaybeSized<T: ?Sized> {
    maybe_sized: T,
}

fn main() {
    // unsized coercion from MaybeSized<[i32; 3]> to MaybeSized<[i32]>
    let ms: &MaybeSized<[i32]> = &MaybeSized { maybe_sized: [1, 2, 3] };
}

So what are the use-cases of this? There aren't any particularly compelling ones, user-defined unsized types are a pretty half-baked feature right now and their limitations outweigh any benefits. They're mentioned here purely for the sake of comprehensiveness.

Fun fact: std::ffi::OsStr and std::path::Path are 2 unsized structs in the standard library that you've probably used before without realizing!

Key Takeaways

  • user-defined unsized types are a half-baked feature right now and their limitations outweigh any benefits

Zero-Sized Types|🔝|

ZSTs sound exotic at first but they're used everywhere.

Unit Type

The most common ZST is the unit type: (). All empty blocks {} evaluate to () and if the block is non-empty but the last expression is discarded with a semicolon ; then it also evaluates to (). Example:

fn main() {
    let a: () = {};
    let b: i32 = {
        5
    };
    let c: () = {
        5;
    };
}

Every function which doesn't have an explicit return type returns () by default.

#![allow(unused)]
fn main() {
// with sugar
fn function() {}

// desugared
fn function() -> () {}
}

Since () is zero bytes all instances of () are the same which makes for some really simple Default, PartialEq, and Ord implementations:

#![allow(unused)]
fn main() {
use std::cmp::Ordering;

impl Default for () {
    fn default() {}
}

impl PartialEq for () {
    fn eq(&self, _other: &()) -> bool {
        true
    }
    fn ne(&self, _other: &()) -> bool {
        false
    }
}

impl Ord for () {
    fn cmp(&self, _other: &()) -> Ordering {
        Ordering::Equal
    }
}
}

The compiler understands () is zero-sized and optimizes away interactions with instances of (). For example, a Vec<()> will never make any heap allocations, and pushing and popping () from the Vec just increments and decrements its len field:

fn main() {
    // zero capacity is all the capacity we need to "store" infinitely many ()
    let mut vec: Vec<()> = Vec::with_capacity(0);
    // causes no heap allocations or vec capacity changes
    vec.push(()); // len++
    vec.push(()); // len++
    vec.push(()); // len++
    vec.pop(); // len--
    assert_eq!(2, vec.len());
}

The above example has no practical applications, but is there any situation where we can take advantage of the above idea in a meaningful way? Surprisingly yes, we can get an efficient HashSet<Key> implementation from a HashMap<Key, Value> by setting the Value to () which is exactly how HashSet in the Rust standard library works:

#![allow(unused)]
fn main() {
// std::collections::HashSet
pub struct HashSet<T> {
    map: HashMap<T, ()>,
}
}

Key Takeaways

  • all instances of a ZST are equal to each other
  • Rust compiler knows to optimize away interactions with ZSTs

User-Defined Unit Structs|🔝|

A unit struct is any struct without any fields, e.g.

#![allow(unused)]
fn main() {
struct Struct;
}

Properties that make unit structs more useful than ():

  • we can implement whatever traits we want on our own unit structs, Rust's trait orphan rules prevent us from implementing traits for () as it's defined in the standard library
  • unit structs can be given meaningful names within the context of our program
  • unit structs, like all structs, are non-Copy by default, which may be important in the context of our program

Never Type|🔝|

The second most common ZST is the never type: !. It's called the never type because it represents computations that never resolve to any value at all.

A couple interesting properties of ! that make it different from ():

  • ! can be coerced into any other type
  • it's not possible to create instances of !

The first interesting property is very useful for ergonomics and allows us to use handy macros like these:

#![allow(unused)]
fn main() {
// nice for quick prototyping
fn example<T>(t: &[T]) -> Vec<T> {
    unimplemented!() // ! coerced to Vec<T>
}

fn example2() -> i32 {
    // we know this parse call will never fail
    match "123".parse::<i32>() {
        Ok(num) => num,
        Err(_) => unreachable!(), // ! coerced to i32
    }
}

fn example3(some_condition: bool) -> &'static str {
    if !some_condition {
        panic!() // ! coerced to &str
    } else {
        "str"
    }
}
}

break, continue, and return expressions also have type !:

#![allow(unused)]
fn main() {
fn example() -> i32 {
    // we can set the type of x to anything here
    // since the block never evaluates to any value
    let x: String = {
        return 123 // ! coerced to String
    };
}

fn example2(nums: &[i32]) -> Vec<i32> {
    let mut filtered = Vec::new();
    for num in nums {
        filtered.push(
            if *num < 0 {
                break // ! coerced to i32
            } else if *num % 2 == 0 {
                *num
            } else {
                continue // ! coerced to i32
            }
        );
    }
    filtered
}
}

The second interesting property of ! allows us to mark certain states as impossible on a type level. Let's take this function signature as an example:

#![allow(unused)]
fn main() {
fn function() -> Result<Success, Error>;
}

We know that if the function returns and was successful the Result will contain some instance of type Success and if it errored Result will contain some instance of type Error. Now let's compare that to this function signature:

#![allow(unused)]
fn main() {
fn function() -> Result<Success, !>;
}

We know that if the function returns and was successful the Result will hold some instance of type Success and if it errored... but wait, it can never error, since it's impossible to create instances of !. Given the above function signature we know this function will never error. How about this function signature:

#![allow(unused)]
fn main() {
fn function() -> Result<!, Error>;
}

The inverse of the previous is now true: if this function returns we know it must have errored as success is impossible.

A practical application of the former example would be the FromStr implementation for String as it's impossible to fail converting a &str into a String:

#![allow(unused)]
#![feature(never_type)]

fn main() {
use std::str::FromStr;

impl FromStr for String {
    type Err = !;
    fn from_str(s: &str) -> Result<String, Self::Err> {
        Ok(String::from(s))
    }
}
}

A practical application of the latter example would be a function that runs an infinite loop that's never meant to return, like a server responding to client requests, unless there's some error:

#![allow(unused)]
#![feature(never_type)]

fn main() {
fn run_server() -> Result<!, ConnectionError> {
    loop {
        let (request, response) = get_request()?;
        let result = request.process();
        response.send(result);
    }
}
}

The feature flag is necessary because while the never type exists and works within Rust internals using it in user-code is still considered experimental.

Key Takeaways

  • ! can be coerced into any other type
  • it's not possible to create instances of ! which we can use to mark certain states as impossible at a type level

User-Defined Pseudo Never Types

While it's not possible to define a type that can coerce to any other type it is possible to define a type which is impossible to create instances of such as an enum without any variants:

#![allow(unused)]
fn main() {
enum Void {}
}

This allows us to remove the feature flag from the previous two examples and implement them using stable Rust:

#![allow(unused)]
fn main() {
enum Void {}

// example 1
impl FromStr for String {
    type Err = Void;
    fn from_str(s: &str) -> Result<String, Self::Err> {
        Ok(String::from(s))
    }
}

// example 2
fn run_server() -> Result<Void, ConnectionError> {
    loop {
        let (request, response) = get_request()?;
        let result = request.process();
        response.send(result);
    }
}
}

This is the technique the Rust standard library uses, as the Err type for the FromStr implementation of String is std::convert::Infallible which is defined as:

#![allow(unused)]
fn main() {
pub enum Infallible {}
}

PhantomData|🔝|

The third most commonly used ZST is probably PhantomData. PhantomData is a zero-sized marker struct which can be used to "mark" a containing struct as having certain properties. It's similar in purpose to its auto marker trait cousins such as Sized, Send, and Sync but being a marker struct is used a little bit differently. Giving a thorough explanation of PhantomData and exploring all of its use-cases is outside the scope of this article so let's only briefly go over a single simple example. Recall this code snippet presented earlier:

#![allow(unused)]
#![feature(negative_impls)]

fn main() {
// this type is Send and Sync
struct Struct;

// opt-out of Send trait
impl !Send for Struct {}

// opt-out of Sync trait
impl !Sync for Struct {}
}

It's unfortunate that we have to use a feature flag, can we accomplish the same result using only stable Rust? As we've learned, a type is only Send and Sync if all of its members are also Send and Sync, so we can add a !Send and !Sync member to Struct like Rc<()>:

#![allow(unused)]
fn main() {
use std::rc::Rc;

// this type is not Send or Sync
struct Struct {
    // adds 8 bytes to every instance
    _not_send_or_sync: Rc<()>,
}
}

This is less than ideal because it adds size to every instance of Struct and we now also have to conjure a Rc<()> from thin air every time we want to create a Struct. Since PhantomData is a ZST it solves both of these problems:

#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::marker::PhantomData;

type NotSendOrSyncPhantom = PhantomData<Rc<()>>;

// this type is not Send or Sync
struct Struct {
    // adds no additional size to instances
    _not_send_or_sync: NotSendOrSyncPhantom,
}
}

Key Takeaways

  • PhantomData is a zero-sized marker struct which can be used to "mark" a containing struct as having certain properties

Conclusion|🔝|

  • only instances of sized types can be placed on the stack, i.e. can be passed around by value
  • instances of unsized types can't be placed on the stack and must be passed around by reference
  • pointers to unsized types are double-width because aside from pointing to data they need to do an extra bit of bookkeeping to also keep track of the data's length or point to a vtable
  • Sized is an "auto" marker trait
  • all generic type parameters are auto-bound with Sized by default
  • if we have a generic function which takes an argument of some T behind a pointer, e.g. &T, Box<T>, Rc<T>, et cetera, then we almost always want to opt-out of the default Sized bound with T: ?Sized
  • leveraging slices and Rust's auto type coercions allows us to write flexible APIs
  • all traits are ?Sized by default
  • Trait: ?Sized is required for impl Trait for dyn Trait
  • we can require Self: Sized on a per-method basis
  • traits bound by Sized can't be made into trait objects
  • Rust doesn't support pointers wider than 2 widths so
    • we can't cast unsized types to trait objects
    • we can't have multi-trait objects, but we can work around this by coalescing multiple traits into a single trait
  • user-defined unsized types are a half-baked feature right now and their limitations outweigh any benefits
  • all instances of a ZST are equal to each other
  • Rust compiler knows to optimize away interactions with ZSTs
  • ! can be coerced into any other type
  • it's not possible to create instances of ! which we can use to mark certain states as impossible at a type level
  • PhantomData is a zero-sized marker struct which can be used to "mark" a containing struct as having certain properties

Discuss|🔝|

Discuss this article on

Further Reading|🔝|

C++오너쉽개념 기초 C++11에서 가져옴

OwnerShip밴다이어그램으로 소유권 이해

link


벤다이어그램 이해

오너쉽 관계를 잘이해하자(구현할 때 중요하다impl)

  • T는 오너쉽을 가지고 있다. T는 하위(&, &mut) 모두 커버되는 SuperSet

    • T is a superset of (& , &mut), denoted by
  • &&mut 은 disjoint set 관계

    • &(reference) and (&mut) are disjoint set

\[T \supseteq Reference, ref mut \]

\[Reference \cup ref mut \]

Reference and Mutable Reference 관련 글

Closures클로져DeepDive

link


클로져Closures 다이어그램으로 이해하기

fnrust

다른언어에서 러스트로 온 좋은 기능들..

link


(rust)const fn/(C++)constexpr

link


C++에서 C++11에서 constexpr도입된 스토리|🔝|

  • (2021년도 댓글)In case you didn’t know, constexpr in C++11, when it was introduced, was extremely limited (basically the function body had to be a single return statement, with recursion and the conditional operator the only ways of flow control available). Now, ten years and three Standard revisions later, it’s much more capable, but Rust hasn’t even existed in stable form that long. Give it time.
    • 몰랐을 경우를 대비해 C++11의 컨스펙스프는 도입 당시 매우 제한적이었습니다(기본적으로 함수 바디는 단일 반환문이어야 했고, 재귀와 조건부 연산자만이 흐름 제어의 유일한 방법이었습니다). 10년이 지난 지금, 훨씬 더 성능이 뛰어나지만 러스트는 그렇게 오랫동안 안정적인 형태로 존재하지도 않았습니다. 시간을 주세요.

const fn 활용법(Rust에 처음 도입된게 )|🔝|


Rust Verion const fn 발전 역사|🔝|


Version 1.82.0 (2024-10-17)|🔝|

==========================

Language


Version 1.79.0 (2024-06-13)

==========================

Language


Version 1.61.0 (2022-05-19)|🔝|

Language

  • [const fn signatures can now include generic trait bounds][93827]
  • [const fn signatures can now use impl Trait in argument and return position][93827]
  • [Function pointers can now be created, cast, and passed around in a const fn][93827]
  • [Recursive calls can now set the value of a function's opaque impl Trait return type][94081]

Version 1.56.0 (2021-10-21)|🔝|

Language

  • [The 2021 Edition is now stable.][rust#88100] See the edition guide for more details.
  • [The pattern in binding @ pattern can now also introduce new bindings.][rust#85305]
  • [Union field access is permitted in const fn.][rust#85769]

Version 1.54.0 (2021-07-29)|🔝|

============================

Language

  • [You can now use macros for values in some built-in attributes.][83366] This primarily allows you to call macros within the #[doc] attribute. For example, to include external documentation in your crate, you can now write the following:

    #![allow(unused)]
    #![doc = include_str!("README.md")]
    fn main() {
    }
  • [You can now cast between unsized slice types (and types which contain unsized slices) in const fn.][85078]

  • [You can now use multiple generic lifetimes with impl Trait where the lifetimes don't explicitly outlive another.][84701] In code this means that you can now have impl Trait<'a, 'b> where as before you could only have impl Trait<'a, 'b> where 'b: 'a.


Version 1.51.0 (2021-03-25)|🔝|

============================

Language

  • [You can now parameterize items such as functions, traits, and structs by constant values in addition to by types and lifetimes.][79135] Also known as "const generics" E.g. you can now write the following. Note: Only values of primitive integers, bool, or char types are currently permitted.

    • [이제 유형별, 수명별 외에도 일정한 값으로 함수, 특성, 구조와 같은 항목을 매개 변수화할 수 있습니다.][79135] "const generics"라고도 하는 이제 다음을 작성할 수 있습니다. 참고: 현재 원시 정수, 불 또는 차 유형의 값만 허용됩니다.
    #![allow(unused)]
    fn main() {
    struct GenericArray<T, const LENGTH: usize> {
        inner: [T; LENGTH]
    }
    
    impl<T, const LENGTH: usize> GenericArray<T, LENGTH> {
        const fn last(&self) -> Option<&T> {
            if LENGTH == 0 {
                None
            } else {
                Some(&self.inner[LENGTH - 1])
            }
        }
    }
    }

Version 1.48.0 (2020-11-19)|🔝|

==========================

The following previously stable methods are now const fn's:

  • [Option::is_some]
  • [Option::is_none]
  • [Option::as_ref]
  • [Result::is_ok]
  • [Result::is_err]
  • [Result::as_ref]
  • [Ordering::reverse]
  • [Ordering::then]

Compatibility Notes

  • [Promotion of references to 'static lifetime inside const fn now follows the same rules as inside a fn body.][75502] In particular, &foo() will not be promoted to 'static lifetime any more inside const fns.

Libraries

  • [mem::forget is now a const fn.][73887]

Version 1.36.0 (2019-07-04)|🔝|

==========================

Language

  • [Non-Lexical Lifetimes are now enabled on the 2015 edition.][59114]
  • [The order of traits in trait objects no longer affects the semantics of that object.][59445] e.g. dyn Send + fmt::Debug is now equivalent to dyn fmt::Debug + Send, where this was previously not the case.

Libraries

  • [HashMap's implementation has been replaced with hashbrown::HashMap implementation.][58623]
  • [TryFromSliceError now implements From<Infallible>.][60318]
  • [mem::needs_drop is now available as a const fn.][60364]
  • [alloc::Layout::from_size_align_unchecked is now available as a const fn.][60370]
  • [Both NonNull::{dangling, cast} are now const fns.][60244]

Version 1.33.0 (2019-02-28)|🔝|

==========================

Language

  • [You can now use the cfg(target_vendor) attribute.][57465] E.g. #[cfg(target_vendor="apple")] fn main() { println!("Hello Apple!"); }
  • [Integer patterns such as in a match expression can now be exhaustive.][56362] E.g. You can have match statement on a u8 that covers 0..=255 and you would no longer be required to have a _ => unreachable!() case.
  • [You can now have multiple patterns in if let and while let expressions.][57532] You can do this with the same syntax as a match expression. E.g.
    enum Creature {
        Crab(String),
        Lobster(String),
        Person(String),
    }
    
    fn main() {
        let state = Creature::Crab("Ferris");
    
        if let Creature::Crab(name) | Creature::Person(name) = state {
            println!("This creature's name is: {}", name);
        }
    }
  • [You can now have irrefutable if let and while let patterns.][57535] Using this feature will by default produce a warning as this behaviour can be unintuitive. E.g. if let _ = 5 {}
  • [You can now use let bindings, assignments, expression statements, and irrefutable pattern destructuring in const functions.][57175]
  • [You can now call unsafe const functions.][57067] E.g.
    #![allow(unused)]
    fn main() {
    const unsafe fn foo() -> i32 { 5 }
    const fn bar() -> i32 {
        unsafe { foo() }
    }
    }
  • [You can now specify multiple attributes in a cfg_attr attribute.][57332] E.g. #[cfg_attr(all(), must_use, optimize)]
  • [You can now specify a specific alignment with the #[repr(packed)] attribute.][57049] E.g. #[repr(packed(2))] struct Foo(i16, i32); is a struct with an alignment of 2 bytes and a size of 6 bytes.
  • [You can now import an item from a module as an _.][56303] This allows you to import a trait's impls, and not have the name in the namespace. E.g.
    #![allow(unused)]
    fn main() {
    use std::io::Read as _;
    
    // Allowed as there is only one `Read` in the module.
    pub trait Read {}
    }
  • [You may now use Rc, Arc, and Pin as method receivers][56805].

Version 1.31.0 (2018-12-06)|🔝|

==========================

Language

  • 🎉 [This version marks the release of the 2018 edition of Rust.][54057] 🎉
  • [New lifetime elision rules now allow for eliding lifetimes in functions and impl headers.][54778] E.g. impl<'a> Reader for BufReader<'a> {} can now be impl Reader for BufReader<'_> {}. Lifetimes are still required to be defined in structs.
  • [You can now define and use const functions.][54835] These are currently a strict minimal subset of the [const fn RFC][RFC-911]. Refer to the [language reference][const-reference] for what exactly is available.

Operator

link


Operator expressions|🔝|

Syntax
OperatorExpression :
      BorrowExpression
   | DereferenceExpression
   | ErrorPropagationExpression
   | NegationExpression
   | ArithmeticOrLogicalExpression
   | ComparisonExpression
   | LazyBooleanExpression
   | TypeCastExpression
   | AssignmentExpression
   | CompoundAssignmentExpression

Operators are defined for built in types by the Rust language. Many of the following operators can also be overloaded using traits in std::ops or std::cmp.

Overflow

Integer operators will panic when they overflow when compiled in debug mode. The -C debug-assertions and -C overflow-checks compiler flags can be used to control this more directly. The following things are considered to be overflow:

  • When +, * or binary - create a value greater than the maximum value, or less than the minimum value that can be stored.
  • Applying unary - to the most negative value of any signed integer type, unless the operand is a literal expression (or a literal expression standing alone inside one or more grouped expressions).
  • Using / or %, where the left-hand argument is the smallest integer of a signed integer type and the right-hand argument is -1. These checks occur even when -C overflow-checks is disabled, for legacy reasons.
  • Using << or >> where the right-hand argument is greater than or equal to the number of bits in the type of the left-hand argument, or is negative.

Note: The exception for literal expressions behind unary - means that forms such as -128_i8 or let j: i8 = -(128) never cause a panic and have the expected value of -128.

In these cases, the literal expression already has the most negative value for its type (for example, 128_i8 has the value -128) because integer literals are truncated to their type per the description in Integer literal expressions.

Negation of these most negative values leaves the value unchanged due to two's complement overflow conventions.

In rustc, these most negative expressions are also ignored by the overflowing_literals lint check.

Borrow operators

Syntax
BorrowExpression :
      (&|&&) Expression
   | (&|&&) mut Expression
   | (&|&&) raw const Expression
   | (&|&&) raw mut Expression

The & (shared borrow) and &mut (mutable borrow) operators are unary prefix operators. When applied to a place expression, this expressions produces a reference (pointer) to the location that the value refers to. The memory location is also placed into a borrowed state for the duration of the reference. For a shared borrow (&), this implies that the place may not be mutated, but it may be read or shared again. For a mutable borrow (&mut), the place may not be accessed in any way until the borrow expires. &mut evaluates its operand in a mutable place expression context. If the & or &mut operators are applied to a value expression, then a temporary value is created.

These operators cannot be overloaded.

#![allow(unused)]
fn main() {
{
    // a temporary with value 7 is created that lasts for this scope.
    let shared_reference = &7;
}
let mut array = [-2, 3, 9];
{
    // Mutably borrows `array` for this scope.
    // `array` may only be used through `mutable_reference`.
    let mutable_reference = &mut array;
}
}

Even though && is a single token (the lazy 'and' operator), when used in the context of borrow expressions it works as two borrows:

#![allow(unused)]
fn main() {
// same meanings:
let a = &&  10;
let a = & & 10;

// same meanings:
let a = &&&&  mut 10;
let a = && && mut 10;
let a = & & & & mut 10;
}

Raw borrow operators

&raw const and &raw mut are the raw borrow operators. The operand expression of these operators is evaluated in place expression context. &raw const expr then creates a const raw pointer of type *const T to the given place, and &raw mut expr creates a mutable raw pointer of type *mut T.

The raw borrow operators must be used instead of a borrow operator whenever the place expression could evaluate to a place that is not properly aligned or does not store a valid value as determined by its type, or whenever creating a reference would introduce incorrect aliasing assumptions. In those situations, using a borrow operator would cause undefined behavior by creating an invalid reference, but a raw pointer may still be constructed.

The following is an example of creating a raw pointer to an unaligned place through a packed struct:

#![allow(unused)]
fn main() {
#[repr(packed)]
struct Packed {
    f1: u8,
    f2: u16,
}

let packed = Packed { f1: 1, f2: 2 };
// `&packed.f2` would create an unaligned reference, and thus be undefined behavior!
let raw_f2 = &raw const packed.f2;
assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);
}

The following is an example of creating a raw pointer to a place that does not contain a valid value:

#![allow(unused)]
fn main() {
use std::mem::MaybeUninit;

struct Demo {
    field: bool,
}

let mut uninit = MaybeUninit::<Demo>::uninit();
// `&uninit.as_mut().field` would create a reference to an uninitialized `bool`,
// and thus be undefined behavior!
let f1_ptr = unsafe { &raw mut (*uninit.as_mut_ptr()).field };
unsafe { f1_ptr.write(true); }
let init = unsafe { uninit.assume_init() };
}

The dereference operator

Syntax
DereferenceExpression :
   * Expression

The * (dereference) operator is also a unary prefix operator. When applied to a pointer it denotes the pointed-to location. If the expression is of type &mut T or *mut T, and is either a local variable, a (nested) field of a local variable or is a mutable place expression, then the resulting memory location can be assigned to. Dereferencing a raw pointer requires unsafe.

On non-pointer types *x is equivalent to *std::ops::Deref::deref(&x) in an immutable place expression context and *std::ops::DerefMut::deref_mut(&mut x) in a mutable place expression context.

#![allow(unused)]
fn main() {
let x = &7;
assert_eq!(*x, 7);
let y = &mut 9;
*y = 11;
assert_eq!(*y, 11);
}

The question mark operator

Syntax
ErrorPropagationExpression :
   Expression ?

The question mark operator (?) unwraps valid values or returns erroneous values, propagating them to the calling function. It is a unary postfix operator that can only be applied to the types Result<T, E> and Option<T>.

When applied to values of the Result<T, E> type, it propagates errors. If the value is Err(e), then it will return Err(From::from(e)) from the enclosing function or closure. If applied to Ok(x), then it will unwrap the value to evaluate to x.

#![allow(unused)]
fn main() {
use std::num::ParseIntError;
fn try_to_parse() -> Result<i32, ParseIntError> {
    let x: i32 = "123".parse()?; // x = 123
    let y: i32 = "24a".parse()?; // returns an Err() immediately
    Ok(x + y)                    // Doesn't run.
}

let res = try_to_parse();
println!("{:?}", res);
assert!(res.is_err())
}

When applied to values of the Option<T> type, it propagates Nones. If the value is None, then it will return None. If applied to Some(x), then it will unwrap the value to evaluate to x.

#![allow(unused)]
fn main() {
fn try_option_some() -> Option<u8> {
    let val = Some(1)?;
    Some(val)
}
assert_eq!(try_option_some(), Some(1));

fn try_option_none() -> Option<u8> {
    let val = None?;
    Some(val)
}
assert_eq!(try_option_none(), None);
}

? cannot be overloaded.

Negation operators

Syntax
NegationExpression :
      - Expression
   | ! Expression

These are the last two unary operators. This table summarizes the behavior of them on primitive types and which traits are used to overload these operators for other types. Remember that signed integers are always represented using two's complement. The operands of all of these operators are evaluated in value expression context so are moved or copied.

SymbolIntegerboolFloating PointOverloading Trait
-Negation*Negationstd::ops::Neg
!Bitwise NOTLogical NOTstd::ops::Not

* Only for signed integer types.

Here are some example of these operators

#![allow(unused)]
fn main() {
let x = 6;
assert_eq!(-x, -6);
assert_eq!(!x, -7);
assert_eq!(true, !false);
}

Arithmetic and Logical Binary Operators

Syntax
ArithmeticOrLogicalExpression :
      Expression + Expression
   | Expression - Expression
   | Expression * Expression
   | Expression / Expression
   | Expression % Expression
   | Expression & Expression
   | Expression | Expression
   | Expression ^ Expression
   | Expression << Expression
   | Expression >> Expression

Binary operators expressions are all written with infix notation. This table summarizes the behavior of arithmetic and logical binary operators on primitive types and which traits are used to overload these operators for other types. Remember that signed integers are always represented using two's complement. The operands of all of these operators are evaluated in value expression context so are moved or copied.

SymbolIntegerboolFloating PointOverloading TraitOverloading Compound Assignment Trait
+AdditionAdditionstd::ops::Addstd::ops::AddAssign
-SubtractionSubtractionstd::ops::Substd::ops::SubAssign
*MultiplicationMultiplicationstd::ops::Mulstd::ops::MulAssign
/Division*†Divisionstd::ops::Divstd::ops::DivAssign
%Remainder**†Remainderstd::ops::Remstd::ops::RemAssign
&Bitwise ANDLogical ANDstd::ops::BitAndstd::ops::BitAndAssign
|Bitwise ORLogical ORstd::ops::BitOrstd::ops::BitOrAssign
^Bitwise XORLogical XORstd::ops::BitXorstd::ops::BitXorAssign
<<Left Shiftstd::ops::Shlstd::ops::ShlAssign
>>Right Shift***std::ops::Shrstd::ops::ShrAssign

* Integer division rounds towards zero.

** Rust uses a remainder defined with truncating division. Given remainder = dividend % divisor, the remainder will have the same sign as the dividend.

*** Arithmetic right shift on signed integer types, logical right shift on unsigned integer types.

† For integer types, division by zero panics.

Here are examples of these operators being used.

#![allow(unused)]
fn main() {
assert_eq!(3 + 6, 9);
assert_eq!(5.5 - 1.25, 4.25);
assert_eq!(-5 * 14, -70);
assert_eq!(14 / 3, 4);
assert_eq!(100 % 7, 2);
assert_eq!(0b1010 & 0b1100, 0b1000);
assert_eq!(0b1010 | 0b1100, 0b1110);
assert_eq!(0b1010 ^ 0b1100, 0b110);
assert_eq!(13 << 3, 104);
assert_eq!(-10 >> 2, -3);
}

Comparison Operators

Syntax
ComparisonExpression :
      Expression == Expression
   | Expression != Expression
   | Expression > Expression
   | Expression < Expression
   | Expression >= Expression
   | Expression <= Expression

Comparison operators are also defined both for primitive types and many types in the standard library. Parentheses are required when chaining comparison operators. For example, the expression a == b == c is invalid and may be written as (a == b) == c.

Unlike arithmetic and logical operators, the traits for overloading these operators are used more generally to show how a type may be compared and will likely be assumed to define actual comparisons by functions that use these traits as bounds. Many functions and macros in the standard library can then use that assumption (although not to ensure safety). Unlike the arithmetic and logical operators above, these operators implicitly take shared borrows of their operands, evaluating them in place expression context:

#![allow(unused)]
fn main() {
let a = 1;
let b = 1;
a == b;
// is equivalent to
::std::cmp::PartialEq::eq(&a, &b);
}

This means that the operands don't have to be moved out of.

SymbolMeaningOverloading method
==Equalstd::cmp::PartialEq::eq
!=Not equalstd::cmp::PartialEq::ne
>Greater thanstd::cmp::PartialOrd::gt
<Less thanstd::cmp::PartialOrd::lt
>=Greater than or equal tostd::cmp::PartialOrd::ge
<=Less than or equal tostd::cmp::PartialOrd::le

Here are examples of the comparison operators being used.

#![allow(unused)]
fn main() {
assert!(123 == 123);
assert!(23 != -12);
assert!(12.5 > 12.2);
assert!([1, 2, 3] < [1, 3, 4]);
assert!('A' <= 'B');
assert!("World" >= "Hello");
}

Lazy boolean operators

Syntax
LazyBooleanExpression :
      Expression || Expression
   | Expression && Expression

The operators || and && may be applied to operands of boolean type. The || operator denotes logical 'or', and the && operator denotes logical 'and'. They differ from | and & in that the right-hand operand is only evaluated when the left-hand operand does not already determine the result of the expression. That is, || only evaluates its right-hand operand when the left-hand operand evaluates to false, and && only when it evaluates to true.

#![allow(unused)]
fn main() {
let x = false || true; // true
let y = false && panic!(); // false, doesn't evaluate `panic!()`
}

Type cast expressions

Syntax
TypeCastExpression :
   Expression as TypeNoBounds

A type cast expression is denoted with the binary operator as.

Executing an as expression casts the value on the left-hand side to the type on the right-hand side.

An example of an as expression:

#![allow(unused)]
fn main() {
fn sum(values: &[f64]) -> f64 { 0.0 }
fn len(values: &[f64]) -> i32 { 0 }
fn average(values: &[f64]) -> f64 {
    let sum: f64 = sum(values);
    let size: f64 = len(values) as f64;
    sum / size
}
}

as can be used to explicitly perform coercions, as well as the following additional casts. Any cast that does not fit either a coercion rule or an entry in the table is a compiler error. Here *T means either *const T or *mut T. m stands for optional mut in reference types and mut or const in pointer types.

Type of eUCast performed by e as U
Integer or Float typeInteger or Float typeNumeric cast
EnumerationInteger typeEnum cast
bool or charInteger typePrimitive to integer cast
u8charu8 to char cast
*T*V where V: Sized *Pointer to pointer cast
*T where T: SizedInteger typePointer to address cast
Integer type*V where V: SizedAddress to pointer cast
&m₁ T*m₂ T **Reference to pointer cast
&m₁ [T; n]*m₂ T **Array to pointer cast
Function itemFunction pointerFunction item to function pointer cast
Function item*V where V: SizedFunction item to pointer cast
Function itemIntegerFunction item to address cast
Function pointer*V where V: SizedFunction pointer to pointer cast
Function pointerIntegerFunction pointer to address cast
Closure ***Function pointerClosure to function pointer cast

* or T and V are compatible unsized types, e.g., both slices, both the same trait object.

** only when m₁ is mut or m₂ is const. Casting mut reference to const pointer is allowed.

*** only for closures that do not capture (close over) any local variables

Semantics

Numeric cast

  • Casting between two integers of the same size (e.g. i32 -> u32) is a no-op (Rust uses 2's complement for negative values of fixed integers)

    #![allow(unused)]
    fn main() {
    assert_eq!(42i8 as u8, 42u8);
    assert_eq!(-1i8 as u8, 255u8);
    assert_eq!(255u8 as i8, -1i8);
    assert_eq!(-1i16 as u16, 65535u16);
    }
  • Casting from a larger integer to a smaller integer (e.g. u32 -> u8) will truncate

    #![allow(unused)]
    fn main() {
    assert_eq!(42u16 as u8, 42u8);
    assert_eq!(1234u16 as u8, 210u8);
    assert_eq!(0xabcdu16 as u8, 0xcdu8);
    
    assert_eq!(-42i16 as i8, -42i8);
    assert_eq!(1234u16 as i8, -46i8);
    assert_eq!(0xabcdi32 as i8, -51i8);
    }
  • Casting from a smaller integer to a larger integer (e.g. u8 -> u32) will

    • zero-extend if the source is unsigned
    • sign-extend if the source is signed
    #![allow(unused)]
    fn main() {
    assert_eq!(42i8 as i16, 42i16);
    assert_eq!(-17i8 as i16, -17i16);
    assert_eq!(0b1000_1010u8 as u16, 0b0000_0000_1000_1010u16, "Zero-extend");
    assert_eq!(0b0000_1010i8 as i16, 0b0000_0000_0000_1010i16, "Sign-extend 0");
    assert_eq!(0b1000_1010u8 as i8 as i16, 0b1111_1111_1000_1010u16 as i16, "Sign-extend 1");
    }
  • Casting from a float to an integer will round the float towards zero

    • NaN will return 0
    • Values larger than the maximum integer value, including INFINITY, will saturate to the maximum value of the integer type.
    • Values smaller than the minimum integer value, including NEG_INFINITY, will saturate to the minimum value of the integer type.
    #![allow(unused)]
    fn main() {
    assert_eq!(42.9f32 as i32, 42);
    assert_eq!(-42.9f32 as i32, -42);
    assert_eq!(42_000_000f32 as i32, 42_000_000);
    assert_eq!(std::f32::NAN as i32, 0);
    assert_eq!(1_000_000_000_000_000f32 as i32, 0x7fffffffi32);
    assert_eq!(std::f32::NEG_INFINITY as i32, -0x80000000i32);
    }
  • Casting from an integer to float will produce the closest possible float *

    • if necessary, rounding is according to roundTiesToEven mode ***
    • on overflow, infinity (of the same sign as the input) is produced
    • note: with the current set of numeric types, overflow can only happen on u128 as f32 for values greater or equal to f32::MAX + (0.5 ULP)
    #![allow(unused)]
    fn main() {
    assert_eq!(1337i32 as f32, 1337f32);
    assert_eq!(123_456_789i32 as f32, 123_456_790f32, "Rounded");
    assert_eq!(0xffffffff_ffffffff_ffffffff_ffffffff_u128 as f32, std::f32::INFINITY);
    }
  • Casting from an f32 to an f64 is perfect and lossless

    #![allow(unused)]
    fn main() {
    assert_eq!(1_234.5f32 as f64, 1_234.5f64);
    assert_eq!(std::f32::INFINITY as f64, std::f64::INFINITY);
    assert!((std::f32::NAN as f64).is_nan());
    }
  • Casting from an f64 to an f32 will produce the closest possible f32 **

    • if necessary, rounding is according to roundTiesToEven mode ***
    • on overflow, infinity (of the same sign as the input) is produced
    #![allow(unused)]
    fn main() {
    assert_eq!(1_234.5f64 as f32, 1_234.5f32);
    assert_eq!(1_234_567_891.123f64 as f32, 1_234_567_890f32, "Rounded");
    assert_eq!(std::f64::INFINITY as f32, std::f32::INFINITY);
    assert!((std::f64::NAN as f32).is_nan());
    }

* if integer-to-float casts with this rounding mode and overflow behavior are not supported natively by the hardware, these casts will likely be slower than expected.

** if f64-to-f32 casts with this rounding mode and overflow behavior are not supported natively by the hardware, these casts will likely be slower than expected.

*** as defined in IEEE 754-2008 §4.3.1: pick the nearest floating point number, preferring the one with an even least significant digit if exactly halfway between two floating point numbers.

Enum cast

Casts an enum to its discriminant, then uses a numeric cast if needed. Casting is limited to the following kinds of enumerations:

#![allow(unused)]
fn main() {
enum Enum { A, B, C }
assert_eq!(Enum::A as i32, 0);
assert_eq!(Enum::B as i32, 1);
assert_eq!(Enum::C as i32, 2);
}

Primitive to integer cast

  • false casts to 0, true casts to 1
  • char casts to the value of the code point, then uses a numeric cast if needed.
#![allow(unused)]
fn main() {
assert_eq!(false as i32, 0);
assert_eq!(true as i32, 1);
assert_eq!('A' as i32, 65);
assert_eq!('Ö' as i32, 214);
}

u8 to char cast

Casts to the char with the corresponding code point.

#![allow(unused)]
fn main() {
assert_eq!(65u8 as char, 'A');
assert_eq!(214u8 as char, 'Ö');
}

Pointer to address cast

Casting from a raw pointer to an integer produces the machine address of the referenced memory. If the integer type is smaller than the pointer type, the address may be truncated; using usize avoids this.

Address to pointer cast

Casting from an integer to a raw pointer interprets the integer as a memory address and produces a pointer referencing that memory.

[!WARNING] This interacts with the Rust memory model, which is still under development. A pointer obtained from this cast may suffer additional restrictions even if it is bitwise equal to a valid pointer. Dereferencing such a pointer may be undefined behavior if aliasing rules are not followed.

A trivial example of sound address arithmetic:

#![allow(unused)]
fn main() {
let mut values: [i32; 2] = [1, 2];
let p1: *mut i32 = values.as_mut_ptr();
let first_address = p1 as usize;
let second_address = first_address + 4; // 4 == size_of::<i32>()
let p2 = second_address as *mut i32;
unsafe {
    *p2 += 1;
}
assert_eq!(values[1], 3);
}

Pointer-to-pointer cast

*const T / *mut T can be cast to *const U / *mut U with the following behavior:

  • If T and U are both sized, the pointer is returned unchanged.

  • If T and U are both unsized, the pointer is also returned unchanged. In particular, the metadata is preserved exactly.

    For instance, a cast from *const [T] to *const [U] preserves the number of elements. Note that, as a consequence, such casts do not necessarily preserve the size of the pointer's referent (e.g., casting *const [u16] to *const [u8] will result in a raw pointer which refers to an object of half the size of the original). The same holds for str and any compound type whose unsized tail is a slice type, such as struct Foo(i32, [u8]) or (u64, Foo).

  • If T is unsized and U is sized, the cast discards all metadata that completes the wide pointer T and produces a thin pointer U consisting of the data part of the unsized pointer.

Assignment expressions

Syntax
AssignmentExpression :
   Expression = Expression

An assignment expression moves a value into a specified place.

An assignment expression consists of a mutable assignee expression, the assignee operand, followed by an equals sign (=) and a value expression, the assigned value operand. In its most basic form, an assignee expression is a place expression, and we discuss this case first. The more general case of destructuring assignment is discussed below, but this case always decomposes into sequential assignments to place expressions, which may be considered the more fundamental case.

Basic assignments

Evaluating assignment expressions begins by evaluating its operands. The assigned value operand is evaluated first, followed by the assignee expression. For destructuring assignment, subexpressions of the assignee expression are evaluated left-to-right.

Note: This is different than other expressions in that the right operand is evaluated before the left one.

It then has the effect of first dropping the value at the assigned place, unless the place is an uninitialized local variable or an uninitialized field of a local variable. Next it either copies or moves the assigned value to the assigned place.

An assignment expression always produces the unit value.

Example:

#![allow(unused)]
fn main() {
let mut x = 0;
let y = 0;
x = y;
}

Destructuring assignments

Destructuring assignment is a counterpart to destructuring pattern matches for variable declaration, permitting assignment to complex values, such as tuples or structs. For instance, we may swap two mutable variables:

#![allow(unused)]
fn main() {
let (mut a, mut b) = (0, 1);
// Swap `a` and `b` using destructuring assignment.
(b, a) = (a, b);
}

In contrast to destructuring declarations using let, patterns may not appear on the left-hand side of an assignment due to syntactic ambiguities. Instead, a group of expressions that correspond to patterns are designated to be assignee expressions, and permitted on the left-hand side of an assignment. Assignee expressions are then desugared to pattern matches followed by sequential assignment. The desugared patterns must be irrefutable: in particular, this means that only slice patterns whose length is known at compile-time, and the trivial slice [..], are permitted for destructuring assignment.

The desugaring method is straightforward, and is illustrated best by example.

#![allow(unused)]
fn main() {
struct Struct { x: u32, y: u32 }
let (mut a, mut b) = (0, 0);
(a, b) = (3, 4);

[a, b] = [3, 4];

Struct { x: a, y: b } = Struct { x: 3, y: 4};

// desugars to:

{
    let (_a, _b) = (3, 4);
    a = _a;
    b = _b;
}

{
    let [_a, _b] = [3, 4];
    a = _a;
    b = _b;
}

{
    let Struct { x: _a, y: _b } = Struct { x: 3, y: 4};
    a = _a;
    b = _b;
}
}

Identifiers are not forbidden from being used multiple times in a single assignee expression.

Underscore expressions and empty range expressions may be used to ignore certain values, without binding them.

Note that default binding modes do not apply for the desugared expression.

Compound assignment expressions

Syntax
CompoundAssignmentExpression :
      Expression += Expression
   | Expression -= Expression
   | Expression *= Expression
   | Expression /= Expression
   | Expression %= Expression
   | Expression &= Expression
   | Expression |= Expression
   | Expression ^= Expression
   | Expression <<= Expression
   | Expression >>= Expression

Compound assignment expressions combine arithmetic and logical binary operators with assignment expressions.

For example:

#![allow(unused)]
fn main() {
let mut x = 5;
x += 1;
assert!(x == 6);
}

The syntax of compound assignment is a mutable place expression, the assigned operand, then one of the operators followed by an = as a single token (no whitespace), and then a value expression, the modifying operand.

Unlike other place operands, the assigned place operand must be a place expression. Attempting to use a value expression is a compiler error rather than promoting it to a temporary.

Evaluation of compound assignment expressions depends on the types of the operators.

If both types are primitives, then the modifying operand will be evaluated first followed by the assigned operand. It will then set the value of the assigned operand's place to the value of performing the operation of the operator with the values of the assigned operand and modifying operand.

Note: This is different than other expressions in that the right operand is evaluated before the left one.

Otherwise, this expression is syntactic sugar for calling the function of the overloading compound assignment trait of the operator (see the table earlier in this chapter). A mutable borrow of the assigned operand is automatically taken.

For example, the following expression statements in example are equivalent:

#![allow(unused)]
fn main() {
struct Addable;
use std::ops::AddAssign;

impl AddAssign<Addable> for Addable {
    /* */
fn add_assign(&mut self, other: Addable) {}
}

fn example() {
let (mut a1, a2) = (Addable, Addable);
  a1 += a2;

let (mut a1, a2) = (Addable, Addable);
  AddAssign::add_assign(&mut a1, a2);
}
}

Like assignment expressions, compound assignment expressions always produce the unit value.

[!WARNING] The evaluation order of operands swaps depending on the types of the operands: with primitive types the right-hand side will get evaluated first, while with non-primitive types the left-hand side will get evaluated first. Try not to write code that depends on the evaluation order of operands in compound assignment expressions. See this test for an example of using this dependency.

C_Operator_Precedence

link


C Operator Precedence

  • The following table lists the precedence and associativity of C operators. Operators are listed top to bottom, in descending precedence.
Precedence Operator Description Associativity
1 ++ -- Suffix/postfix increment and decrement Left-to-right
() Function call
[] Array subscripting
. Structure and union member access
-> Structure and union member access through pointer
(type){list} Compound literal(C99)
2 ++ -- Prefix increment and decrement[note 1] Right-to-left
+ - Unary plus and minus
! ~ Logical NOT and bitwise NOT
(type) Cast
* Indirection (dereference)
& Address-of
sizeof Size-of[note 2]
_Alignof Alignment requirement(C11)
3 * / % Multiplication, division, and remainder Left-to-right
4 + - Addition and subtraction
5 << >> Bitwise left shift and right shift
6 < <= For relational operators < and ≤ respectively
> >= For relational operators > and ≥ respectively
7 == != For relational = and ≠ respectively
8 & Bitwise AND
9 ^ Bitwise XOR (exclusive or)
10 | Bitwise OR (inclusive or)
11 && Logical AND
12 || Logical OR
13 ?: Ternary conditional[note 3] Right-to-left
14[note 4] = Simple assignment
+= -= Assignment by sum and difference
*= /= %= Assignment by product, quotient, and remainder
<<= >>= Assignment by bitwise left shift and right shift
&= ^= |= Assignment by bitwise AND, XOR, and OR
15 , Comma Left-to-right
  1. The operand of prefix ++ and -- can't be a type cast. This rule grammatically forbids some expressions that would be semantically invalid anyway. Some compilers ignore this rule and detect the invalidity semantically.
  2. The operand of sizeof can't be a type cast: the expression sizeof (int) * p is unambiguously interpreted as (sizeof(int)) * p, but not sizeof((int)*p).
  3. The expression in the middle of the conditional operator (between ? and :) is parsed as if parenthesized: its precedence relative to ?: is ignored.
  4. Assignment operators' left operands must be unary (level-2 non-cast) expressions. This rule grammatically forbids some expressions that would be semantically invalid anyway. Many compilers ignore this rule and detect the invalidity semantically. For example, e = a < d ? a++ : a = d is an expression that cannot be parsed because of this rule. However, many compilers ignore this rule and parse it as e = ( ((a < d) ? (a++) : a) = d ), and then give an error because it is semantically invalid.

When parsing an expression, an operator which is listed on some row will be bound tighter (as if by parentheses) to its arguments than any operator that is listed on a row further below it. For example, the expression *p++ is parsed as *(p++), and not as (*p)++.

Operators that are in the same cell (there may be several rows of operators listed in a cell) are evaluated with the same precedence, in the given direction. For example, the expression a=b=c is parsed as a=(b=c), and not as (a=b)=c because of right-to-left associativity.

Notes

Precedence and associativity are independent from order of evaluation.

The standard itself doesn't specify precedence levels. They are derived from the grammar.

In C++, the conditional operator has the same precedence as assignment operators, and prefix ++ and -- and assignment operators don't have the restrictions about their operands.

Associativity specification is redundant for unary operators and is only shown for completeness: unary prefix operators always associate right-to-left (sizeof ++*p is sizeof(++(*p))) and unary postfix operators always associate left-to-right (a[1][2]++ is ((a[1])[2])++). Note that the associativity is meaningful for member access operators, even though they are grouped with unary postfix operators: a.b++ is parsed (a.b)++ and not a.(b++).

References

  • C17 standard (ISO/IEC 9899:2018):
  • A.2.1 Expressions
  • C11 standard (ISO/IEC 9899:2011):
  • A.2.1 Expressions
  • C99 standard (ISO/IEC 9899:1999):
  • A.2.1 Expressions
  • C89/C90 standard (ISO/IEC 9899:1990):
  • A.1.2.1 Expressions

See also

Order of evaluation of operator arguments at run time.

Common operators
assignment increment
decrement
arithmetic logical comparison member
access
other

a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b

++a
--a
a++
a--

+a
-a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b

!a
a && b
a || b

a == b
a != b
a < b
a > b
a <= b
a >= b

a[b]
*a
&a
a->b
a.b

a(...)
a, b
(type) a
a ? b : c
sizeof


_Alignof
(since C11)

C++ documentation for C++ operator precedence
  • https://en.cppreference.com/w/c/language/operator_precedence

Logical_Operators

link


Rust Code

fn main() {
    println!("true && true = {}", true && true);
    println!("true || true = {}", true || true);
    println!("true || !true = {}", true || !true);
    println!("true && !true = {}", true && !true);
    println!("!true || true = {}", !true || true);
    println!("true || !true = {}", true || !true);
    println!(
        "!(true && true) == (!true || !true)) = {}",
        (!(true && true) == (!true || !true))
    );
    println!(
        "!(true || true) == (!true && !true) = {}",
        (!(true || true) == (!true && !true))
    );
}
  • Result
true && true = true
true || true = true
true || !true = true
true && !true = false
!true || true = true
true || !true = true
!(true && true) == (!true || !true)) = true
!(true || true) == (!true && !true) = true

Bitwise_Operators

link


Rust Code

// Rust
use std::u32::MAX;

fn main() {
    let x = 0x55; // 0101 0101 (10진수로 85)
    let y = 0x66; // 0110 0110
    let usize_test_x = 0x55;
    let u32_test_x: u32 = 0x55;
    let u32max_test_x: u32 = MAX;
    let x_i8: i8 = 3; // 0011
    let y_i8: i8 = 10; // 1010

    let x_01 = 0x1; // 0001
    let y_02 = 0x2; // 0010

    // Bitwise NOT
    let x02 = 6;
    assert_eq!(-x02, -6);
    assert_eq!(!x02, -7);

    println!(
        "~x : {} // Bitwise NOT 숫자 1이 올라가면서 -86으로 바뀜(85 -> -86)",
        !x
    ); //  앞에가 다 1로 가득참.1111 1010 1010 인데 강제 형변환 된면서 값이 이상해진듯
    println!("~x : {}", !usize_test_x); //
    println!("~x : {}", !u32_test_x); //
    println!("~x : {}", u32max_test_x); //
    println!("~x : {}", !u32max_test_x); //
    println!("~x : {}", !x_i8); // - 0100
    println!("~x : {}", !y_i8); // -11 - 1011
    println!("x & y : {} // Bitwise AND & ", x & y); // 0100 0100
    println!("x | y : {} // Bitwise OR | ", x | y); // 0111 0111
    println!("x ^ y : {} // Bitwise XOR ^", x ^ y); // 0011 0011
    println!("x01 << y02 : {}  // left shift", x_01 << y_02); // 0100
    println!("x01 >> y02 : {}   // right shift", x_01 >> y_02); // 0000
}
  • Result
# rust 에서는 C언어에서 ~x 이걸 !x 이렇게 표현한다. not gate
~x : -86 // Bitwise NOT 숫자 1이 올라가면서 -86으로 바뀜(85 -> -86)
~x : -86
~x : 4294967210
~x : 4294967295
~x : 0
~x : -4
~x : -11
x & y : 68 // Bitwise AND &
x | y : 119 // Bitwise OR |
x ^ y : 51 // Bitwise XOR ^
x01 << y02 : 4  // left shift
x01 >> y02 : 0   // right shift

C언어

#include <stdio.h>

int main(void) {
    int a = 0x55; // 0101 0101
    int b = 0x66; // 0110 0110
    int x = 0x1; // 0001
    int y = 0x2; // 0010
    int x02 = 0x4; // 0100
    int y02 = 0x1; // 0001
    printf("~a = %d\n", ~a); // 강제형변환 10101010 (unsigned) = 11111111111111111111111110101010 (signed)
    printf("a & b = %d\n", a & b); // 0100 0100
    printf("a | b = %d\n", a | b); // 0111 0111
    printf("a ^ b = %d\n", a ^ b); // 0011 0011
    printf("\nx << y =  %d  // left shift\n", x << y); //  0100
    printf("x >> y = %d  // right shift\n", x >> y); //  0000
    printf("\nx02 << y02 = %d  // left shift\n", x02 << y02); //  1000
    printf("x02 >> y02 = %d  // right shift\n", x02 >> y02); //  0010

    return 0;
}
  • Result
clang -pedantic -pthread -pedantic-errors -lm -Wall -Wextra -ggdb -o ./target/main ./src/main.c
./target/main

~a = -86
a & b = 68
a | b = 119
a ^ b = 51

x << y =  4  // left shift
x >> y = 0   // right shift

x02 << y02 = 8  // left shift
x02 >> y02 = 2  // right shift

~a가 -86으로 평가 되는 이유|🔝|

  • 주어진 C 코드에서 ~a가 -86으로 평가되는 이유를 분해해 보겠습니다.

  • ~ 연산자란 무엇인가요?

  • C에서 ~ 연산자는 비트 단위 NOT 연산자입니다. 연산자는 피연산자의 모든 비트를 뒤집습니다. 즉, 피연산자의 이진 표현에서 각 비트에 대해 ~ 연산자는 이를 반전시킵니다.

  • 변수 a의 이진 표현 변수는 16진수 값 0x55로 초기화됩니다. 이를 이진법으로 변환합니다:

  • Let's break down the process of why ~a evaluates to -86 in the given C code.

    • What is the ~ operator?
    • In C, the ~ operator is the bitwise NOT operator. It flips all the bits of its operand. This means that for each bit in the binary representation of the operand, the ~ operator will invert it.
    • Binary representation of a The variable a is initialized with the hexadecimal value 0x55. Let's convert this to binary:
0x55 in binary is 01010101
  • Applying the bitwise NOT operator

  • Now, let's apply the ~ operator to the binary representation of a:

  • 비트 단위 NOT 연산자 적용

  • 이제 ~ 연산자를 a의 이진 표현에 적용해 보겠습니다:

~01010101 = 10101010
  • Converting the result back to decimal

  • The binary result 10101010 can be converted back to decimal:

  • 10101010 in decimal is 170

  • However, in the given code, the output is -86. This is because the ~ operator is applied to an unsigned integer (0x55), but the result is then implicitly converted to a signed integer.

    • 결과를 십진수로 다시 변환하기
  • 이진 결과 10101010은 다시 10진수로 변환할 수 있습니다:

  • 10101010 소수점 이하는 170입니다

  • 그러나 주어진 코드에서 출력은 -86입니다. 이는 ~ 연산자가 부호 없는 정수(0x55)에 적용되지만 결과는 암시적으로 부호 있는 정수로 변환되기 때문입니다.


  • Implicit conversion to signed integer

  • When the ~ operator is applied to an unsigned integer, the result is still an unsigned integer. However, when this result is assigned to a signed integer variable (in this case, the printf format string %d expects a signed integer), the compiler performs an implicit conversion.

  • The implicit conversion from unsigned to signed integer is done by sign-extending the bits. In this case, the binary result 10101010 is sign-extended to a 32-bit signed integer:

10101010 (unsigned) = 11111111111111111111111110101010 (signed)
  • 부호 있는 정수로의 암묵적 변환

  • ~ 연산자를 부호화되지 않은 정수에 적용하면 결과는 여전히 부호화되지 않은 정수입니다. 그러나 이 결과가 부호화된 정수 변수에 할당되면(이 경우 printf 형식 문자열 %d은 부호화된 정수를 기대합니다) 컴파일러는 암묵적 변환을 수행합니다.

  • 암시적인 부호 없는 정수에서 부호 있는 정수로의 변환은 비트를 부호 확장하여 수행됩니다. 이 경우 이진 결과 10101010은 32비트 부호 있는 정수로 부호 확장됩니다:

10101010 (unsigned) = 11111111111111111111111110101010 (signed)

Rust Lifetime개념 깊게deep dive

(210417)A Lightweight Formalism for Reference Lifetimes and Borrowing in Rust

  • A Lightweight Formalism for Reference Lifetimes and Borrowing in Rust
  • Abstract
    • Rust is a relatively new programming language that has gained significant traction since its v1.0 release in 2015. Rust aims to be a systems language that competes with C/C++. A claimed advantage of Rust is a strong focus on memory safety without garbage collection. This is primarily achieved through two concepts, namely, reference lifetimes and borrowing. Both of these are well-known ideas stemming from the literature on region-based memory management and linearity/uniqueness. Rust brings both of these ideas together to form a coherent programming model. Furthermore, Rust has a strong focus on stack-allocated data and, like C/C++ but unlike Java, permits references to local variables.
    • Type checking in Rust can be viewed as a two-phase process: First, a traditional type checker operates in a flow-insensitive fashion; second, a borrow checker enforces an ownership invariant using a flow-sensitive analysis. In this article, we present a lightweight formalism that captures these two phases using a flow-sensitive type system that enforces “type and borrow safety.” In particular, programs that are type and borrow safe will not attempt to dereference dangling pointers. Our calculus core captures many aspects of Rust, including copy- and move-semantics, mutable borrowing, reborrowing, partial moves, and lifetimes. In particular, it remains sufficiently lightweight to be easily digested and understood and, we argue, still captures the salient aspects of reference lifetimes and borrowing. Furthermore, extensions to the core can easily add more complex features (e.g., control-flow, tuples, method invocation). We provide a soundness proof to verify our key claims of the calculus. We also provide a reference implementation in Java with which we have model checked our calculus using over 500B input programs. We have also fuzz tested the Rust compiler using our calculus against 2B programs and, to date, found one confirmed compiler bug and several other possible issues.
    • 추상
      • Rust는 2015년 v1.0이 출시된 이후 상당한 관심을 받고 있는 비교적 새로운 프로그래밍 언어입니다. Rust는 C/C++와 경쟁하는 시스템 언어를 목표로 합니다. Rust의 장점은 쓰레기 수거 없이 메모리 안전에 중점을 둔다는 것입니다. 이는 주로 기준 수명과 차입이라는 두 가지 개념을 통해 달성됩니다. 이 두 가지 모두 지역 기반 메모리 관리와 선형성/고유성에 관한 문헌에서 비롯된 잘 알려진 아이디어입니다. Rust는 이 두 가지 아이디어를 모두 결합하여 일관된 프로그래밍 모델을 형성합니다. 또한 Rust는 스택 할당 데이터에 중점을 두고 있으며 C/C++와 마찬가지로 로컬 변수에 대한 참조를 허용합니다.
      • Rust의 유형 확인은 2단계 프로세스로 볼 수 있습니다: 첫째, 기존 유형 검사기는 흐름에 민감하지 않은 방식으로 작동하고, 둘째, 차입 검사기는 흐름에 민감한 분석을 사용하여 소유권 불변량을 강제합니다. 이 글에서는 "유형 및 차입 안전"을 강제하는 흐름에 민감한 유형 시스템을 사용하여 이 두 단계를 캡처하는 가벼운 형식주의를 제시합니다. 특히 유형 및 차입 안전한 프로그램은 달링 포인터를 참조 해제하려고 시도하지 않습니다. 우리의 미적분 코어는 복사 및 이동 시맨틱, 돌연변이 차입, 재융자, 부분 이동, 수명 등 Rust의 많은 측면을 캡처합니다. 특히, 쉽게 소화하고 이해할 수 있을 만큼 충분히 가볍고 여전히 참조 수명과 차입의 두드러진 측면을 캡처한다고 주장합니다. 또한 코어 확장은 더 복잡한 기능(예: 제어 흐름, 튜플, 방법 호출)을 쉽게 추가할 수 있습니다. 우리는 미적분에 대한 주요 주장을 검증하기 위한 건전성 증명을 제공합니다. 또한 500B 이상의 입력 프로그램을 사용하여 미적분을 모델링한 Java의 참조 구현을 제공합니다. 또한 2B 프로그램에 대해 미적분을 사용하여 Rust 컴파일러를 퍼지 테스트했으며, 현재까지 하나의 확인된 컴파일러 버그와 기타 몇 가지 가능한 문제를 발견했습니다.

Algorithm알고리즘

link



알고리즘의 4단계|🔝|

  • 알고리즘은 4단계를 기억해야한다.
      1. 정렬(Sort)
      1. 검색(Search)
      1. 문자열 패턴 매칭(SPM: String Pattern Matching)
      1. 계산(Calculation)

Data를 저장하는 패턴|🔝|

자료 구조[🔝]

https://github.com/YoungHaKim7/c_project/tree/main/exercise/002stack

  • 영어 출처 https://en.wikipedia.org/wiki/Association_list
자료 구조(Well-known data structures)
유형(Type) 컬렉션(Collection) , 컨테이너(Container)
추상ADT
Abstract Data Type
연관 배열(Associative array), 우선 순위 덱(Priority Deque), 덱(Deque), 리스트(List),
멀티맵, 우선순위 큐(Priority Queue), 큐(Queue),
집합 (멀티셋, 분리 집합),
스택(stack)
Associative array(Multimap, Retrieval Data Structure), List, StackQueue(Double-ended queue), Priority queue(Double-ended priority queue), Set(Multiset, Disjoint-set)
배열(Array) 비트 배열(Bit Array), 환형 배열(Circular array), 동적 배열(Dynamic Array),
해시 테이블(Hash Table), 해시드 어레이 트리(Hashed Array Tree), 희소 배열(Sparse array)
연결형(Linked) 연관 리스트(Association list),

연결 리스트(Linked List) - 단일연결(Singly Linked List), 이중연결(Doubly Linked List), 원형 연결(Circular Linked List)

Association list,
Linked list, Skip list, Unrolled linked list, XOR linked list
트리(Trees) B 트리,
이진 탐색 트리(AA, AVL, 레드-블랙, 자가 균형, splay)
힙(이진 힙, 피보나치) ,
R 트리( R*, R+, 힐버트),
트리(해시 트리)

B-tree, Binary search tree(AA tree, AVL tree, Red–black tree, Self-balancing tree, Splay tree),
Heap(Binary heap, Binomial heap, Fibonacci heap),
R-tree(R* tree, R+ tree, Hilbert R-tree), Trie Hash tree
그래프(Graphs) 이진 결정 다이어그램
Binary decision diagram, Directed acyclic graph, Directed acyclic word graph


자료구조란? | 배열(array) | 리스트(list) | 스택( stack) | 큐(queue) | 데큐(deque) | 트리(tree) | 그래프(graph) | 혀니C코딩|🔝|

  • https://youtu.be/RZHYuAhUrwE?si=_1XPXvbxiE9p3RLG
큰 분류 장점 단점 쓰기 적합한 곳
1. 선형구조 - 배열(array) Random Access
읽기전용이구만 ㅋㅋ
접속이 아주 많은 경우는
Array가 적합하다.
데이터에 접근할 일이 많은 경우
배열 중간에 있는거 꺼낼때
전체 copy와 move가
일어나서 성능 저하 ㅠㅠ
Overhead발생 ㅠㅠ
Overhead가 커질수록 성능 저하
읽기 전용이 무지 많은 곳
시작점 index[0]
- 리스트(list)
(단일, 이중, 원형)
Array단점 보완
중간삭제 추가시 Overhead가 X
오버헤드 없음(x)
중간 index번호100번같이 데이터 삭제 추가가
편하다.head하고 tail값만 수정해주면 됨. 대박 편함.
원소 하나하나가 따로 따로
Memory를 많이 사용함 ㅠㅠ
Random Access가 불가능
무조건 head부터
추적해서 들어가야한다. ㅠㅠ
데이터 추가 /삭제가
아주 많이 빈번한곳
시작점 head
- 스택
(stack)
메모장 복사하기 되돌리기 생각하면 됨
- 큐
(queue)
Printer출력
연결리스트가 성능이 좋다.
- 데크
(Deque)
stack+queue형태
앞뒤양쪽방향에서
추가,삭제가 가능
2. 비선형구조 - 트리 부모와 자식관계
방향이 존재하는
방향 그래프
시작점root
- 그래프 시작점x
정해진 방향은x
네비게이션 생각하면 됨

array 배열|🔝|

[1,  2,  3,  4,  5,  6]
[0] [1] [2] [3] [4] [5]
// index

Random Access가 가능

list리스트(head가 필요- 첫번째 원소의 주소가 저장됨)|🔝|

  • 원형리스트는 null이 없다.(꼬리와 머리가 이어져 있기 때문에 ^^)
  • 다음 주소를 저장할 Pointer가 필요함
    • 마지막에 저장값이 없다면 tail에 null를 저장함
head에 시작 주소
       ┌-----┓       ┌-----┓
       | head|       | head|
       └┭----┘    ┌> └┭----┘
        ┌--┓      |   ┌--┓
node -> |1 |      |   |2 |
        └--┘      |   └--┘
       ┌--┴--┓    |  ┌--┴--┓   반복
       | tail| ---┘  | tail| ---┘
       └-----┘       └-----┘
       tail에는 다음 주소
       8 bytes

  • [자료구조] 단일 연결 리스트 2시간 라이브 스트리밍[첫 번째 특강] | 2023년 10월 15일 오후 12시
    • https://www.youtube.com/live/2-7vBNP4YFI?si=G0Qsjwp1Niv_bYvX

Stack스택(+)|🔝|

  • 접시를 쌓는다 생각하면 됨 한쪽 방향으로만 데이터가 쌓임
    • LIFO(Last In First Out)

Pop(자료 뺄때, -)|🔝|

  • 접시에서 상단부터 빼는거 생각하면 됨.stack과 pop
    • LIFO(Last In First Out)

큐queue(마트에서 줄서는거 생각하면됨)|🔝|

  • Print종이 나오는거 생각하면됨. First처음 들어오면 처음 나가는거 굿
  • FIFO(First In First Out)
  • 큐queue구현은 연결list가 오버헤드없이 구현해야 최적화 잘됨.
    • 삽입(enqueue)
    • 삭제(dequeue)
      • Difference between "enqueue" and "dequeue"
        • https://stackoverflow.com/questions/16433397/difference-between-enqueue-and-dequeue

데크(Deque)(스택과 큐가 합쳐진 형태)|🔝|

Tree트리|🔝|

           root
           1
        
        2     3

   4   5     6   7

Graph그래프(상위root가 없다.)|🔝|

   자유로운 영혼들
   1     2     5

     3     4

Stack 자료구조 | C언어 코드 완벽 구현 | 배열을 이용한 스택 | 삽입(push), 삭제(pop), 출력(print), 초기화(clear) 혀니C코딩|🔝|

  • https://youtu.be/1PFFgRcZLAk?si=6Da6I9xvcgkzo0sS

Rust_FFI

Rust_FFI_C언어

link


hello world(C_FFI)_printf(C)

code ex코드예시

use libc::printf;

fn main() {
    unsafe {
        let print_x = (r#"hello c__FFI(Rust Lang)"#.as_ptr()) as *const i8;
        printf(print_x);
    }
}
  • Result
hello c__FFI(Rust Lang)⏎   

Rust(ComputerSystem,CS)

디지털 포렌식 관련 자료 및 CS자료 어마무시하다

Dive into Systems Print Version, No Starch Press, August 2022 (ISBN-13: 9781718501362).

Upper-level course topicChapters for background reading
Architecture5, 11
Compilers6, 7, 8, 9, 10, 11, 12
Database systems11, 14, 15
Networking4, 13, 14
Operating systems11, 13, 14
Parallel and distributed systems11, 13, 14, 15
--
C Programming and
debugging references
2 , 3
  • Dive into Systems: A Gentle Introduction to Computer Systems Suzanne J. Matthews

Learing the Shell

Dive into Systems Print Version, August 2022

By the C, by the C, by the Beatutiful C

link


Getting Started Programming in C

#include <math.h>
#include <stdio.h>

int main(void) {
  // statements end in a semicolon(;)
  printf("Hello World\n");
  printf("sqrt(4) is %f\n", sqrt(4));
}
Hello World
sqrt(4) is 2.000000

IntegerDisplay정수표시(CS)

link


two's complement encoding(2의 보수)|🔝|

fn main() {
    let raw_two_complement = 0xB; // 1011
    let two_val = !raw_two_complement + 1; // 0100 + 0001
    println!("컴퓨터는 0과 1뿐이라서..");
    println!("마이너스로 빼기를 구현하기 위해 2의 보수 개념이 필요하다.");
    println!("0xB(10진수=11)의 2의 보수 : {}", two_val); // 0101    11(10진법) -> -11 로 변함
}
  • Result

컴퓨터는 0과 1뿐이라서..
마이너스로 빼기를 구현하기 위해 2의 보수 개념이 필요하다.
0xB(10진수=11)의 2의 보수 : -11

rust_release

link


Rust Edition 모아보기|🔝|

Rust Relese 노트 미리 알아보기|🔝|

Rust Version(2015년부터 러스트 역사 확인하기)|🔝|

rust_1_85_Rust Edition 2024

Rust 2024

Info
RFC#3501
Release version1.85.0

link


(241130기준)현재는 nightly로 사용가능한듯 찾아보자


Rust2024 Edition (2025년 beta버젼에 출시 예정250109 beta version에서 사용가능)


Rust 2024 주요 3가지 목표(중요한 3가지)

1Bring the Async Rust experience closer to parity with sync Rust
2Resolve the biggest blockers to Linux building on stable Rust
3Rust 2024 Edition

Rust 2024 목표(그외에 23가지 목표)

  • Goal 러스트 에디션 2024 핵심 목표외 23가지 목표
1"Stabilizable" prototype for expanded const generics
2Administrator-provided reasons for yanked crates
3Assemble project goal slate
4Associated type position impl trait
5Begin resolving cargo-semver-checks blockers for merging into cargo
6Const traits
7Ergonomic ref-counting
8Explore sandboxed build scripts
9Expose experimental LLVM features for automatic differentiation and GPU offloading
10Extend pubgrub to match cargo's dependency resolution
11Implement "merged doctests" to save doctest time
12Make Rustdoc Search easier to learn
13Next-generation trait solver
14Optimizing Clippy & linting
15Patterns of empty types
16Scalable Polonius support on nightly
17Stabilize cargo-script
18Stabilize doc_cfg
19Stabilize parallel front end
20Survey tools suitability for Std safety verification
21Testing infra + contributors for a-mir-formality
22Use annotate-snippets for rustc diagnostic output
23User-wide build cache

Asynchrony & Iteration & Fallibility(async(await) & gen(for) & try(match))|🔝|

-ASYNCHRONYITERATIONFALLIBILITY
CONTEXTasync { }gen { }try { }
EFFECTyieldthrow
FORWARD.awaityield from?
COMPLETEspawn/block_onformatch

Async Rust roadmap|🔝|

YearLanguage
2019Async fns
2019-2022Ecosystem development
2023Async fn in traits
2024Async closures, generators....

Language

The following chapters detail changes to the language in the 2024 Edition.

RPIT lifetime capture rules

This chapter describes changes related to the Lifetime Capture Rules 2024 introduced in RFC 3498, including how to use opaque type precise capturing (introduced in RFC 3617) to migrate your code.

Summary

  • In Rust 2024, all in-scope generic parameters, including lifetime parameters, are implicitly captured when the use<..> bound is not present.
  • Uses of the Captures trick (Captures<..> bounds) and of the outlives trick (e.g. '_ bounds) can be replaced by use<..> bounds (in all editions) or removed entirely (in Rust 2024).

Details

Capturing

Capturing a generic parameter in an RPIT (return-position impl Trait) opaque type allows for that parameter to be used in the corresponding hidden type. In Rust 1.82, we added use<..> bounds that allow specifying explicitly which generic parameters to capture. Those will be helpful for migrating your code to Rust 2024, and will be helpful in this chapter for explaining how the edition-specific implicit capturing rules work. These use<..> bounds look like this:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
fn capture<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> {
    //                                ~~~~~~~~~~~~~~~~~~~~~~~
    //                             This is the RPIT opaque type.
    //
    //                                It captures `'a` and `T`.
    (x, y)
  //~~~~~~
  // The hidden type is: `(&'a (), T)`.
  //
  // This type can use `'a` and `T` because they were captured.
}
}

The generic parameters that are captured affect how the opaque type can be used. E.g., this is an error because the lifetime is captured despite the fact that the hidden type does not use the lifetime:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
fn capture<'a>(_: &'a ()) -> impl Sized + use<'a> {}

fn test<'a>(x: &'a ()) -> impl Sized + 'static {
    capture(x)
    //~^ ERROR lifetime may not live long enough
}
}

Conversely, this is OK:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
fn capture<'a>(_: &'a ()) -> impl Sized + use<> {}

fn test<'a>(x: &'a ()) -> impl Sized + 'static {
    capture(x) //~ OK
}
}

Edition-specific rules when no use<..> bound is present

If the use<..> bound is not present, then the compiler uses edition-specific rules to decide which in-scope generic parameters to capture implicitly.

In all editions, all in-scope type and const generic parameters are captured implicitly when the use<..> bound is not present. E.g.:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
fn f_implicit<T, const C: usize>() -> impl Sized {}
//                                    ~~~~~~~~~~
//                         No `use<..>` bound is present here.
//
// In all editions, the above is equivalent to:
fn f_explicit<T, const C: usize>() -> impl Sized + use<T, C> {}
}

In Rust 2021 and earlier editions, when the use<..> bound is not present, generic lifetime parameters are only captured when they appear syntactically within a bound in RPIT opaque types in the signature of bare functions and associated functions and methods within inherent impls. However, starting in Rust 2024, these in-scope generic lifetime parameters are unconditionally captured. E.g.:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
fn f_implicit(_: &()) -> impl Sized {}
// In Rust 2021 and earlier, the above is equivalent to:
fn f_2021(_: &()) -> impl Sized + use<> {}
// In Rust 2024 and later, it's equivalent to:
fn f_2024(_: &()) -> impl Sized + use<'_> {}
}

This makes the behavior consistent with RPIT opaque types in the signature of associated functions and methods within trait impls, uses of RPIT within trait definitions (RPITIT), and opaque Future types created by async fn, all of which implicitly capture all in-scope generic lifetime parameters in all editions when the use<..> bound is not present.

Outer generic parameters

Generic parameters from an outer impl are considered to be in scope when deciding what is implicitly captured. E.g.:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
struct S<T, const C: usize>((T, [(); C]));
impl<T, const C: usize> S<T, C> {
//   ~~~~~~~~~~~~~~~~~
// These generic parameters are in scope.
    fn f_implicit<U>() -> impl Sized {}
    //            ~       ~~~~~~~~~~
    //            ^ This generic is in scope too.
    //                    ^
    //                    |
    //     No `use<..>` bound is present here.
    //
    // In all editions, it's equivalent to:
    fn f_explicit<U>() -> impl Sized + use<T, U, C> {}
}
}

Lifetimes from higher-ranked binders

Similarly, generic lifetime parameters introduced into scope by a higher-ranked for<..> binder are considered to be in scope. E.g.:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
trait Tr<'a> { type Ty; }
impl Tr<'_> for () { type Ty = (); }

fn f_implicit() -> impl for<'a> Tr<'a, Ty = impl Copy> {}
// In Rust 2021 and earlier, the above is equivalent to:
fn f_2021() -> impl for<'a> Tr<'a, Ty = impl Copy + use<>> {}
// In Rust 2024 and later, it's equivalent to:
//fn f_2024() -> impl for<'a> Tr<'a, Ty = impl Copy + use<'a>> {}
//                                        ~~~~~~~~~~~~~~~~~~~~
// However, note that the capturing of higher-ranked lifetimes in
// nested opaque types is not yet supported.
}

Argument position impl Trait (APIT)

Anonymous (i.e. unnamed) generic parameters created by the use of APIT (argument position impl Trait) are considered to be in scope. E.g.:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
fn f_implicit(_: impl Sized) -> impl Sized {}
//               ~~~~~~~~~~
//           This is called APIT.
//
// The above is *roughly* equivalent to:
fn f_explicit<_0: Sized>(_: _0) -> impl Sized + use<_0> {}
}

Note that the former is not exactly equivalent to the latter because, by naming the generic parameter, turbofish syntax can now be used to provide an argument for it. There is no way to explicitly include an anonymous generic parameter in a use<..> bound other than by converting it to a named generic parameter.

Migration

Migrating while avoiding overcapturing

The impl_trait_overcaptures lint flags RPIT opaque types that will capture additional lifetimes in Rust 2024. This lint is part of the rust-2024-compatibility lint group which is automatically applied when running cargo fix --edition. In most cases, the lint can automatically insert use<..> bounds where needed such that no additional lifetimes are captured in Rust 2024.

To migrate your code to be compatible with Rust 2024, run:

cargo fix --edition

For example, this will change:

#![allow(unused)]
fn main() {
fn f<'a>(x: &'a ()) -> impl Sized { *x }
}

...into:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
fn f<'a>(x: &'a ()) -> impl Sized + use<> { *x }
}

Without this use<> bound, in Rust 2024, the opaque type would capture the 'a lifetime parameter. By adding this bound, the migration lint preserves the existing semantics.

Migrating cases involving APIT

In some cases, the lint cannot make the change automatically because a generic parameter needs to be given a name so that it can appear within a use<..> bound. In these cases, the lint will alert you that a change may need to be made manually. E.g., given:

#![allow(unused)]
fn main() {
fn f<'a>(x: &'a (), y: impl Sized) -> impl Sized { (*x, y) }
//   ^^                ~~~~~~~~~~
//               This is a use of APIT.
//
//~^ WARN `impl Sized` will capture more lifetimes than possibly intended in edition 2024
//~| NOTE specifically, this lifetime is in scope but not mentioned in the type's bounds

fn test<'a>(x: &'a (), y: ()) -> impl Sized + 'static {
    f(x, y)
}
}

The code cannot be converted automatically because of the use of APIT and the fact that the generic type parameter must be named in the use<..> bound. To convert this code to Rust 2024 without capturing the lifetime, you must name that type parameter. E.g.:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
#![deny(impl_trait_overcaptures)]
fn f<'a, T: Sized>(x: &'a (), y: T) -> impl Sized + use<T> { (*x, y) }
//       ~~~~~~~~
// The type parameter has been named here.

fn test<'a>(x: &'a (), y: ()) -> impl Sized + use<> {
    f(x, y)
}
}

Note that this changes the API of the function slightly as a type argument can now be explicitly provided for this parameter using turbofish syntax. If this is undesired, you might consider instead whether you can simply continue to omit the use<..> bound and allow the lifetime to be captured. This might be particularly desirable if you might in the future want to use that lifetime in the hidden type and would like to save space for that.

Migrating away from the Captures trick

Prior to the introduction of precise capturing use<..> bounds in Rust 1.82, correctly capturing a lifetime in an RPIT opaque type often required using the Captures trick. E.g.:

#![allow(unused)]
fn main() {
#[doc(hidden)]
pub trait Captures<T: ?Sized> {}
impl<T: ?Sized, U: ?Sized> Captures<T> for U {}

fn f<'a, T>(x: &'a (), y: T) -> impl Sized + Captures<(&'a (), T)> {
//                                           ~~~~~~~~~~~~~~~~~~~~~
//                            This is called the `Captures` trick.
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
    f(t, x);
}
}

With the use<..> bound syntax, the Captures trick is no longer needed and can be replaced with the following in all editions:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
fn f<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> {
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
    f(t, x);
}
}

In Rust 2024, the use<..> bound can often be omitted entirely, and the above can be written simply as:

#![allow(unused)]
fn main() {
#![feature(lifetime_capture_rules_2024)]
fn f<'a, T>(x: &'a (), y: T) -> impl Sized {
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
    f(t, x);
}
}

There is no automatic migration for this, and the Captures trick still works in Rust 2024, but you might want to consider migrating code manually away from using this old trick.

Migrating away from the outlives trick

Prior to the introduction of precise capturing use<..> bounds in Rust 1.82, it was common to use the "outlives trick" when a lifetime needed to be used in the hidden type of some opaque. E.g.:

#![allow(unused)]
fn main() {
fn f<'a, T: 'a>(x: &'a (), y: T) -> impl Sized + 'a {
    //    ~~~~                                 ~~~~
    //    ^                     This is the outlives trick.
    //    |
    // This bound is needed only for the trick.
    (x, y)
//  ~~~~~~
// The hidden type is `(&'a (), T)`.
}
}

This trick was less baroque than the Captures trick, but also less correct. As we can see in the example above, even though any lifetime components within T are independent from the lifetime 'a, we're required to add a T: 'a bound in order to make the trick work. This created undue and surprising restrictions on callers.

Using precise capturing, you can write the above instead, in all editions, as:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
fn f<T>(x: &(), y: T) -> impl Sized + use<'_, T> {
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
   f(t, x);
}
}

In Rust 2024, the use<..> bound can often be omitted entirely, and the above can be written simply as:

#![allow(unused)]
fn main() {
#![feature(precise_capturing)]
#![feature(lifetime_capture_rules_2024)]
fn f<T>(x: &(), y: T) -> impl Sized {
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
   f(t, x);
}
}

There is no automatic migration for this, and the outlives trick still works in Rust 2024, but you might want to consider migrating code manually away from using this old trick.

if let temporary scope

Summary

  • In an if let $pat = $expr { .. } else { .. } expression, the temporary values generated from evaluating $expr will be dropped before the program enters the else branch instead of after.

Details

The 2024 Edition changes the drop scope of temporary values in the scrutinee1 of an if let expression. This is intended to help reduce the potentially unexpected behavior involved with the temporary living for too long.

Before 2024, the temporaries could be extended beyond the if let expression itself. For example:

#![allow(unused)]
fn main() {
// Before 2024
use std::sync::RwLock;

fn f(value: &RwLock<Option<bool>>) {
    if let Some(x) = *value.read().unwrap() {
        println!("value is {x}");
    } else {
        let mut v = value.write().unwrap();
        if v.is_none() {
            *v = Some(true);
        }
    }
    // <--- Read lock is dropped here in 2021
}
}

In this example, the temporary read lock generated by the call to value.read() will not be dropped until after the if let expression (that is, after the else block). In the case where the else block is executed, this causes a deadlock when it attempts to acquire a write lock.

The 2024 Edition shortens the lifetime of the temporaries to the point where the then-block is completely evaluated or the program control enters the else block.

#![allow(unused)]
fn main() {
// Starting with 2024
use std::sync::RwLock;

fn f(value: &RwLock<Option<bool>>) {
    if let Some(x) = *value.read().unwrap() {
        println!("value is {x}");
    }
    // <--- Read lock is dropped here in 2024
    else {
        let mut s = value.write().unwrap();
        if s.is_none() {
            *s = Some(true);
        }
    }
}
}

See the temporary scope rules for more information about how temporary scopes are extended. See the tail expression temporary scope chapter for a similar change made to tail expressions.

1

The scrutinee is the expression being matched on in the if let expression.

Migration

It is always safe to rewrite if let with a match. The temporaries of the match scrutinee are extended past the end of the match expression (typically to the end of the statement), which is the same as the 2021 behavior of if let.

The if_let_rescope lint suggests a fix when a lifetime issue arises due to this change or the lint detects that a temporary value with a custom, non-trivial Drop destructor is generated from the scrutinee of the if let. For instance, the earlier example may be rewritten into the following when the suggestion from cargo fix is accepted:

#![allow(unused)]
fn main() {
use std::sync::RwLock;
fn f(value: &RwLock<Option<bool>>) {
    match *value.read().unwrap() {
        Some(x) => {
            println!("value is {x}");
        }
        _ => {
            let mut s = value.write().unwrap();
            if s.is_none() {
                *s = Some(true);
            }
        }
    }
    // <--- Read lock is dropped here in both 2021 and 2024
}
}

In this particular example, that's probably not what you want due to the aforementioned deadlock! However, some scenarios may be assuming that the temporaries are held past the else clause, in which case you may want to retain the old behavior.

The if_let_rescope lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

After the migration, it is recommended that you review all of the changes of if let to match and decide what is the behavior that you need with respect to when temporaries are dropped. If you determine that the change is unnecessary, then you can revert the change back to if let.

If you want to manually inspect these warnings without performing the edition migration, you can enable the lint with:

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(if_let_rescope)]
}

Tail expression temporary scope

Summary

  • Temporary values generated in evaluation of the tail expression of a function or closure body, or a block are now dropped before local variables.

Details

The 2024 Edition changes the drop order of temporary values in tail expressions. It often comes as a surprise that, before the 2024 Edition, temporary values in tail expressions are dropped later than the local variable bindings, as in the following example:

#![allow(unused)]
fn main() {
// Before 2024
use std::cell::RefCell;
fn f() -> usize {
    let c = RefCell::new("..");
    c.borrow().len() // error[E0597]: `c` does not live long enough
}
}

This yields the following error with the 2021 Edition:

error[E0597]: `c` does not live long enough
 --> src/lib.rs:4:5
  |
3 |     let c = RefCell::new("..");
  |         - binding `c` declared here
4 |     c.borrow().len() // error[E0597]: `c` does not live long enough
  |     ^---------
  |     |
  |     borrowed value does not live long enough
  |     a temporary with access to the borrow is created here ...
5 | }
  | -
  | |
  | `c` dropped here while still borrowed
  | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>`
  |
  = note: the temporary is part of an expression at the end of a block;
          consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
  |
4 |     let x = c.borrow().len(); x // error[E0597]: `c` does not live long enough
  |     +++++++                 +++

For more information about this error, try `rustc --explain E0597`.

In 2021 the local variable c is dropped before the temporary created by c.borrow(). The 2024 Edition changes this so that the temporary value c.borrow() is dropped first, followed by dropping the local variable c, allowing the code to compile as expected.

See the temporary scope rules for more information about how temporary scopes are extended. See the if let temporary scope chapter for a similar change made to if let expressions.

Migration

Unfortunately, there are no semantics-preserving rewrites to shorten the lifetime for temporary values in tail expressions1. The tail_expr_drop_order lint detects if a temporary value with a custom, non-trivial Drop destructor is generated in a tail expression. Warnings from this lint will appear when running cargo fix --edition, but will otherwise not automatically make any changes. It is recommended to manually inspect the warnings and determine whether or not you need to make any adjustments.

If you want to manually inspect these warnings without performing the edition migration, you can enable the lint with:

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(tail_expr_drop_order)]
}
1

Details are documented at RFC 3606

Match ergonomics

This is a placeholder, docs coming soon!

Summary

Details

Migration

Unsafe extern blocks

Summary

Details

Rust 1.82 added the ability in all editions to mark extern blocks with the unsafe keyword.1 Adding the unsafe keyword helps to emphasize that it is the responsibility of the author of the extern block to ensure that the signatures are correct. If the signatures are not correct, then it may result in undefined behavior.

The syntax for an unsafe extern block looks like this:

#![allow(unused)]
fn main() {
unsafe extern "C" {
    // sqrt (from libm) may be called with any `f64`
    pub safe fn sqrt(x: f64) -> f64;

    // strlen (from libc) requires a valid pointer,
    // so we mark it as being an unsafe fn
    pub unsafe fn strlen(p: *const std::ffi::c_char) -> usize;

    // this function doesn't say safe or unsafe, so it defaults to unsafe
    pub fn free(p: *mut core::ffi::c_void);

    pub safe static IMPORTANT_BYTES: [u8; 256];
}
}

In addition to being able to mark an extern block as unsafe, you can also specify if individual items in the extern block are safe or unsafe. Items marked as safe can be used without an unsafe block.

Starting with the 2024 Edition, it is now required to include the unsafe keyword on an extern block. This is intended to make it very clear that there are safety requirements that must be upheld by the extern definitions.

1

See RFC 3484 for the original proposal.

Migration

The missing_unsafe_on_extern lint can update extern blocks to add the unsafe keyword. The lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Just beware that this automatic migration will not be able to verify that the signatures in the extern block are correct. It is still your responsibility to manually review their definition.

Alternatively, you can manually enable the lint to find places where there are unsafe blocks that need to be updated.

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(missing_unsafe_on_extern)]
}

Unsafe attributes

Summary

Details

Rust 1.82 added the ability in all editions to mark certain attributes as unsafe to indicate that they have soundness requirements that must be upheld.1 The syntax for an unsafe attribute looks like this:

#![allow(unused)]
fn main() {
// SAFETY: there is no other global function of this name
#[unsafe(no_mangle)]
pub fn example() {}
}

Marking the attribute with unsafe highlights that there are safety requirements that must be upheld that the compiler cannot verify on its own.

Starting with the 2024 Edition, it is now required to mark these attributes as unsafe. The following section describes the safety requirements for these attributes.

1

See RFC 3325 for the original proposal.

Safety requirements

The no_mangle, export_name, and link_section attributes influence the symbol names and linking behavior of items. Care must be taken to ensure that these attributes are used correctly.

Because the set of symbols across all linked libraries is a global namespace, there can be issues if there is a symbol name collision between libraries. Typically this isn't an issue for normally defined functions because symbol mangling helps ensure that the symbol name is unique. However, attributes like export_name can upset that assumption of uniqueness.

For example, in previous editions the following crashes on most Unix-like platforms despite containing only safe code:

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

#[export_name = "malloc"]
fn foo() -> usize { 1 }

In the 2024 Edition, it is now required to mark these attributes as unsafe to emphasize that it is required to ensure that the symbol is defined correctly:

#![allow(unused)]
fn main() {
// SAFETY: There should only be a single definition of the loop symbol.
#[unsafe(export_name="loop")]
fn arduino_loop() {
    // ...
}
}

Migration

The unsafe_attr_outside_unsafe lint can update these attributes to use the unsafe(...) format. The lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Just beware that this automatic migration will not be able to verify that these attributes are being used correctly. It is still your responsibility to manually review their usage.

Alternatively, you can manually enable the lint to find places where these attributes need to be updated.

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(unsafe_attr_outside_unsafe)]
}

unsafe_op_in_unsafe_fn warning

Summary

  • The unsafe_op_in_unsafe_fn lint now warns by default. This warning detects calls to unsafe operations in unsafe functions without an explicit unsafe block.

Details

The unsafe_op_in_unsafe_fn lint will fire if there are unsafe operations in an unsafe function without an explicit unsafe {} block.

#![allow(unused)]
fn main() {
#![warn(unsafe_op_in_unsafe_fn)]
unsafe fn get_unchecked<T>(x: &[T], i: usize) -> &T {
  x.get_unchecked(i) // WARNING: requires unsafe block
}
}

The solution is to wrap any unsafe operations in an unsafe block:

#![allow(unused)]
fn main() {
#![deny(unsafe_op_in_unsafe_fn)]
unsafe fn get_unchecked<T>(x: &[T], i: usize) -> &T {
  unsafe { x.get_unchecked(i) }
}
}

This change is intended to help protect against accidental use of unsafe operations in an unsafe function. The unsafe function keyword was performing two roles. One was to declare that calling the function requires unsafe, and that the caller is responsible to uphold additional safety requirements. The other role was to allow the use of unsafe operations inside of the function. This second role was determined to be too risky without explicit unsafe blocks.

More information and motivation may be found in RFC #2585.

Migration

The unsafe_op_in_unsafe_fn lint is part of the rust-2024-compatibility lint group. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Alternatively, you can manually enable the lint to find places where unsafe blocks need to be added, or switch it to allow to silence the lint completely.

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(unsafe_op_in_unsafe_fn)]
}

Disallow references to static mut

Summary

  • The static_mut_refs lint level is now deny by default. This checks for taking a shared or mutable reference to a static mut.

Details

The static_mut_refs lint detects taking a reference to a static mut. In the 2024 Edition, this lint is now deny by default to emphasize that you should avoid making these references.

#![allow(unused)]
fn main() {
static mut X: i32 = 23;
static mut Y: i32 = 24;

unsafe {
    let y = &X;             // ERROR: shared reference to mutable static
    let ref x = X;          // ERROR: shared reference to mutable static
    let (x, y) = (&X, &Y);  // ERROR: shared reference to mutable static
}
}

Merely taking such a reference in violation of Rust's mutability XOR aliasing requirement has always been instantaneous undefined behavior, even if the reference is never read from or written to. Furthermore, upholding mutability XOR aliasing for a static mut requires reasoning about your code globally, which can be particularly difficult in the face of reentrancy and/or multithreading.

Note that there are some cases where implicit references are automatically created without a visible & operator. For example, these situations will also trigger the lint:

#![allow(unused)]
fn main() {
static mut NUMS: &[u8; 3] = &[0, 1, 2];

unsafe {
    println!("{NUMS:?}");   // ERROR: shared reference to mutable static
    let n = NUMS.len();     // ERROR: shared reference to mutable static
}
}

Alternatives

Wherever possible, it is strongly recommended to use instead an immutable static of a type that provides interior mutability behind some locally-reasoned abstraction (which greatly reduces the complexity of ensuring that Rust's mutability XOR aliasing requirement is upheld).

In situations where no locally-reasoned abstraction is possible and you are therefore compelled still to reason globally about accesses to your static variable, you must now use raw pointers such as can be obtained via the &raw const or &raw mut operators. By first obtaining a raw pointer rather than directly taking a reference, (the safety requirements of) accesses through that pointer will be more familiar to unsafe developers and can be deferred until/limited to smaller regions of code.

Migration

There is no automatic migration to fix these references to static mut. To avoid undefined behavior you must rewrite your code to use a different approach as recommended in the Alternatives section.

Never type fallback change

Summary

Details

When the compiler sees a value of type ! (never) in a coercion site, it implicitly inserts a coercion to allow the type checker to infer any type:

#![allow(unused)]
fn main() {
#![feature(never_type)]
// This:
let x: u8 = panic!();

// ...is (essentially) turned by the compiler into:
let x: u8 = absurd(panic!());

// ...where `absurd` is the following function
// (it's sound because `!` always marks unreachable code):
fn absurd<T>(x: !) -> T { x }
}

This can lead to compilation errors if the type cannot be inferred:

#![allow(unused)]
fn main() {
#![feature(never_type)]
fn absurd<T>(x: !) -> T { x }
// This:
{ panic!() };

// ...gets turned into this:
{ absurd(panic!()) }; //~ ERROR can't infer the type of `absurd`
}

To prevent such errors, the compiler remembers where it inserted absurd calls, and if it can't infer the type, it uses the fallback type instead:

#![allow(unused)]
fn main() {
#![feature(never_type)]
fn absurd<T>(x: !) -> T { x }
type Fallback = /* An arbitrarily selected type! */ !;
{ absurd::<Fallback>(panic!()) }
}

This is what is known as "never type fallback".

Historically, the fallback type has been () (unit). This caused ! to spontaneously coerce to () even when the compiler would not infer () without the fallback. That was confusing and has prevented the stabilization of the ! type.

In the 2024 edition, the fallback type is now !. (We plan to make this change across all editions at a later date.) This makes things work more intuitively. Now when you pass ! and there is no reason to coerce it to something else, it is kept as !.

In some cases your code might depend on the fallback type being (), so this can cause compilation errors or changes in behavior.

never_type_fallback_flowing_into_unsafe

The default level of the never_type_fallback_flowing_into_unsafe lint has been raised from warn to deny in the 2024 Edition. This lint helps detect a particular interaction with the fallback to ! and unsafe code which may lead to undefined behavior. See the link for a complete description.

Migration

There is no automatic fix, but there is automatic detection of code that will be broken by the edition change. While still on a previous edition you will see warnings if your code will be broken.

The fix is to specify the type explicitly so that the fallback type is not used. Unfortunately, it might not be trivial to see which type needs to be specified.

One of the most common patterns broken by this change is using f()?; where f is generic over the Ok-part of the return type:

#![allow(unused)]
fn main() {
#![allow(dependency_on_unit_never_type_fallback)]
fn outer<T>(x: T) -> Result<T, ()> {
fn f<T: Default>() -> Result<T, ()> {
    Ok(T::default())
}

f()?;
Ok(x)
}
}

You might think that, in this example, type T can't be inferred. However, due to the current desugaring of the ? operator, it was inferred as (), and it will now be inferred as !.

To fix the issue you need to specify the T type explicitly:

#![allow(unused)]
fn main() {
#![deny(dependency_on_unit_never_type_fallback)]
fn outer<T>(x: T) -> Result<T, ()> {
fn f<T: Default>() -> Result<T, ()> {
    Ok(T::default())
}
f::<()>()?;
// ...or:
() = f()?;
Ok(x)
}
}

Another relatively common case is panicking in a closure:

#![allow(unused)]
fn main() {
#![allow(dependency_on_unit_never_type_fallback)]
trait Unit {}
impl Unit for () {}

fn run<R: Unit>(f: impl FnOnce() -> R) {
    f();
}

run(|| panic!());
}

Previously ! from the panic! coerced to () which implements Unit. However now the ! is kept as ! so this code fails because ! doesn't implement Unit. To fix this you can specify the return type of the closure:

#![allow(unused)]
fn main() {
#![deny(dependency_on_unit_never_type_fallback)]
trait Unit {}
impl Unit for () {}

fn run<R: Unit>(f: impl FnOnce() -> R) {
    f();
}
run(|| -> () { panic!() });
}

A similar case to that of f()? can be seen when using a !-typed expression in one branch and a function with an unconstrained return type in the other:

#![allow(unused)]
fn main() {
#![allow(dependency_on_unit_never_type_fallback)]
if true {
    Default::default()
} else {
    return
};
}

Previously () was inferred as the return type of Default::default() because ! from return was spuriously coerced to (). Now, ! will be inferred instead causing this code to not compile because ! does not implement Default.

Again, this can be fixed by specifying the type explicitly:

#![allow(unused)]
fn main() {
#![deny(dependency_on_unit_never_type_fallback)]
() = if true {
    Default::default()
} else {
    return
};

// ...or:

if true {
    <() as Default>::default()
} else {
    return
};
}

Macro Fragment Specifiers

Summary

  • The expr fragment specifier now also supports const and _ expressions.
  • The expr_2021 fragment specifier has been added for backwards compatibility.

Details

As new syntax is added to Rust, existing macro_rules fragment specifiers are sometimes not allowed to match on the new syntax in order to retain backwards compatibility. Supporting the new syntax in the old fragment specifiers is sometimes deferred until the next edition, which provides an opportunity to update them.

Indeed this happened with const expressions added in 1.79 and _ expressions added in 1.59. In the 2021 Edition and earlier, the expr fragment specifier does not match those expressions. This is because you may have a scenario like:

macro_rules! example {
    ($e:expr) => { println!("first rule"); };
    (const $e:expr) => { println!("second rule"); };
}

fn main() {
    example!(const { 1 + 1 });
}

Here, in the 2021 Edition, the macro will match the second rule. If earlier editions had changed expr to match the newly introduced const expressions, then it would match the first rule, which would be a breaking change.

In the 2024 Edition, expr specifiers now also match const and _ expressions. To support the old behavior, the expr_2021 fragment specifier has been added which does not match the new expressions.

Migration

The edition_2024_expr_fragment_specifier lint will change all uses of the expr specifier to expr_2021 to ensure that the behavior of existing macros does not change. The lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

In most cases, you will likely want to keep the expr specifier instead, in order to support the new expressions. You will need to review your macro to determine if there are other rules that would otherwise match with const or _ and determine if there is a conflict. If you want the new behavior, just revert any changes made by the lint.

Alternatively, you can manually enable the lint to find macros where you may need to update the expr specifier.

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(edition_2024_expr_fragment_specifier)]
}

Missing macro fragment specifiers

Summary

Details

The missing_fragment_specifier lint detects a situation when an unused pattern in a macro_rules! macro definition has a meta-variable (e.g. $e) that is not followed by a fragment specifier (e.g. :expr). This is now a hard error in the 2024 Edition.

macro_rules! foo {
   () => {};
   ($name) => { }; // ERROR: missing fragment specifier
}

fn main() {
   foo!();
}

Calling the macro with arguments that would match a rule with a missing specifier (e.g., foo!($name)) is a hard error in all editions. However, simply defining a macro with missing fragment specifiers is not, though we did add a lint in Rust 1.17.

We'd like to make this a hard error in all editions, but there would be too much breakage right now. So we're starting by making this a hard error in Rust 2024.1

1

The lint is marked as a "future-incompatible" warning to indicate that it may become a hard error in all editions in a future release. See #40107 for more information.

Migration

To migrate your code to the 2024 Edition, remove the unused matcher rule from the macro. The missing_fragment_specifier lint is on by default in all editions, and should alert you to macros with this issue.

There is no automatic migration for this change. We expect that this style of macro is extremely rare. The lint has been a future-incompatibility lint since Rust 1.17, a deny-by-default lint since Rust 1.20, and since Rust 1.82, it has warned about dependencies that are using this pattern.

gen keyword

Summary

Details

The gen keyword has been reserved as part of RFC #3513 to introduce "gen blocks" in a future release of Rust. gen blocks will provide a way to make it easier to write certain kinds of iterators. Reserving the keyword now will make it easier to stabilize gen blocks before the next edition.

Migration

Introducing the gen keyword can cause a problem for any identifiers that are already called gen. For example, any variable or function name called gen would clash with the new keyword. To overcome this, Rust supports the r# prefix for a raw identifier, which allows identifiers to overlap with keywords.

The keyword_idents_2024 lint will automatically modify any identifier named gen to be r#gen so that code continues to work on both editions. This lint is part of the rust-2024-compatibility lint group, which will automatically be applied when running cargo fix --edition. To migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

For example, this will change:

fn gen() {
    println!("generating!");
}

fn main() {
    gen();
}

to be:

fn r#gen() {
    println!("generating!");
}

fn main() {
    r#gen();
}

Alternatively, you can manually enable the lint to find places where gen identifiers need to be modified to r#gen:

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(keyword_idents_2024)]
}

Reserved syntax

Summary

  • Unprefixed guarded strings of the form #"foo"# are reserved for future use.
  • Two or more # characters are reserved for future use.

Details

RFC 3593 reserved syntax in the 2024 Edition for guarded string literals that do not have a prefix to make room for possible future language changes. The 2021 Edition reserved syntax for guarded strings with a prefix, such as ident##"foo"##. The 2024 Edition extends that to also reserve strings without the ident prefix.

There are two reserved syntaxes:

  • One or more # characters immediately followed by a string literal.
  • Two or more # characters in a row (not separated by whitespace).

This reservation is done across an edition boundary because of interactions with tokenization and macros. For example, consider this macro:

#![allow(unused)]
fn main() {
macro_rules! demo {
    ( $a:tt ) => { println!("one token") };
    ( $a:tt $b:tt $c:tt ) => { println!("three tokens") };
}

demo!("foo");
demo!(r#"foo"#);
demo!(#"foo"#);
demo!(###)
}

Prior to the 2024 Edition, this produces:

one token
one token
three tokens
three tokens

Starting in the 2024 Edition, the #"foo"# line and the ### line now generates a compile error because those forms are now reserved.

Migration

The rust_2024_guarded_string_incompatible_syntax lint will identify any tokens that match the reserved syntax, and will suggest a modification to insert spaces where necessary to ensure the tokens continue to be parsed separately.

The lint is part of the rust-2024-compatibility lint group which is included in the automatic edition migration. In order to migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Alternatively, you can manually enable the lint to find macro calls where you may need to update the tokens:

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(rust_2024_guarded_string_incompatible_syntax)]
}

Standard library

The following chapters detail changes to the standard library in the 2024 Edition.

Additions to the prelude

Summary

  • The Future and IntoFuture traits are now part of the prelude.
  • This might make calls to trait methods ambiguous which could make some code fail to compile.
  • RustcEncodable and RustcDecodable have been removed from the prelude.

Details

The prelude of the standard library is the module containing everything that is automatically imported in every module. It contains commonly used items such as Option, Vec, drop, and Clone.

The Rust compiler prioritizes any manually imported items over those from the prelude, to make sure additions to the prelude will not break any existing code. For example, if you have a crate or module called example containing a pub struct Option;, then use example::*; will make Option unambiguously refer to the one from example; not the one from the standard library.

However, adding a trait to the prelude can break existing code in a subtle way. For example, a call to x.poll() which comes from a MyPoller trait might fail to compile if std's Future is also imported, because the call to poll is now ambiguous and could come from either trait.

As a solution, Rust 2024 will use a new prelude. It's identical to the current one, except for the following changes:

RustcEncodable and RustcDecodable removal

RustcEncodable and RustcDecodable are two undocumented derive macros that have been removed from the prelude. These were deprecated before Rust 1.0, but remained within the standard library prelude. The 2024 Edition has removed these from the prelude since they are not expected to be used.

If in the unlikely case there is a project still using these, it is recommended to switch to a serialization library, such as those found on crates.io.

Migration

Conflicting trait methods

When two traits that are in scope have the same method name, it is ambiguous which trait method should be used. For example:

trait MyPoller {
    // This name is the same as the `poll` method on the `Future` trait from `std`.
    fn poll(&self) {
        println!("polling");
    }
}

impl<T> MyPoller for T {}

fn main() {
    // Pin<&mut async {}> implements both `std::future::Future` and `MyPoller`.
    // If both traits are in scope (as would be the case in Rust 2024),
    // then it becomes ambiguous which `poll` method to call
    core::pin::pin!(async {}).poll();
}

We can fix this so that it works on all editions by using fully qualified syntax:

fn main() {
    // Now it is clear which trait method we're referring to
    <_ as MyPoller>::poll(&core::pin::pin!(async {}));
}

The rust_2024_prelude_collisions lint will automatically modify any ambiguous method calls to use fully qualified syntax. This lint is part of the rust-2024-compatibility lint group, which will automatically be applied when running cargo fix --edition. To migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

Alternatively, you can manually enable the lint to find places where these qualifications need to be added:

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(rust_2024_prelude_collisions)]
}

RustcEncodable and RustcDecodable

It is strongly recommended that you migrate to a different serialization library if you are still using these. However, these derive macros are still available in the standard library, they are just required to be imported from the older prelude now:

#![allow(unused)]
fn main() {
#[allow(soft_unstable)]
use core::prelude::v1::{RustcDecodable, RustcEncodable};
}

There is no automatic migration for this change; you will need to make the update manually.

Add IntoIterator for Box<[T]>

Summary

  • Boxed slices implement IntoIterator in all editions.
  • Calls to IntoIterator::into_iter are hidden in editions prior to 2024 when using method call syntax (i.e., boxed_slice.into_iter()). So, boxed_slice.into_iter() still resolves to (&(*boxed_slice)).into_iter() as it has before.
  • boxed_slice.into_iter() changes meaning to call IntoIterator::into_iter in Rust 2024.

Details

Until Rust 1.80, IntoIterator was not implemented for boxed slices. In prior versions, if you called .into_iter() on a boxed slice, the method call would automatically dereference from Box<[T]> to &[T], and return an iterator that yielded references of &T. For example, the following worked in prior versions:

#![allow(unused)]
fn main() {
// Example of behavior in previous editions.
let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
// Note: .into_iter() was required in versions older than 1.80
for x in my_boxed_slice.into_iter() {
    // x is of type &u32 in editions prior to 2024
}
}

In Rust 1.80, implementations of IntoIterator were added for boxed slices. This allows iterating over elements of the slice by-value instead of by-reference:

#![allow(unused)]
fn main() {
// NEW as of 1.80, all editions
let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
for x in my_boxed_slice { // notice no need for calling .into_iter()
    // x is of type u32
}
}

This example is allowed on all editions because previously this was an error since for loops do not automatically dereference like the .into_iter() method call does.

However, this would normally be a breaking change because existing code that manually called .into_iter() on a boxed slice would change from having an iterator over references to an iterator over values. To resolve this problem, method calls of .into_iter() on boxed slices have edition-dependent behavior. In editions before 2024, it continues to return an iterator over references, and starting in Edition 2024 it returns an iterator over values.

#![allow(unused)]
fn main() {
// Example of changed behavior in Edition 2024
let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
// Example of old code that still manually calls .into_iter()
for x in my_boxed_slice.into_iter() {
    // x is now type u32 in Edition 2024
}
}

Migration

The boxed_slice_into_iter lint will automatically modify any calls to .into_iter() on boxed slices to call .iter() instead to retain the old behavior of yielding references. This lint is part of the rust-2024-compatibility lint group, which will automatically be applied when running cargo fix --edition. To migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

For example, this will change:

fn main() {
    let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
    for x in my_boxed_slice.into_iter() {
        // x is of type &u32
    }
}

to be:

fn main() {
    let my_boxed_slice: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
    for x in my_boxed_slice.iter() {
        // x is of type &u32
    }
}

The boxed_slice_into_iter lint is defaulted to warn on all editions, so unless you have manually silenced the lint, you should already see it before you migrate.

Unsafe functions

Summary

Details

Over time it has become evident that certain functions in the standard library should have been marked as unsafe. However, adding unsafe to a function can be a breaking change since it requires existing code to be placed in an unsafe block. To avoid the breaking change, these functions are marked as unsafe starting in the 2024 Edition, while not requiring unsafe in previous editions.

std::env::{set_var, remove_var}

It can be unsound to call std::env::set_var or std::env::remove_var in a multi-threaded program due to safety limitations of the way the process environment is handled on some platforms. The standard library originally defined these as safe functions, but it was later determined that was not correct.

It is important to ensure that these functions are not called when any other thread might be running. See the Safety section of the function documentation for more details.

std::os::unix::process::CommandExt::before_exec

The std::os::unix::process::CommandExt::before_exec function is a unix-specific function which provides a way to run a closure before calling exec. This function was deprecated in the 1.37 release, and replaced with pre_exec which does the same thing, but is marked as unsafe.

Even though before_exec is deprecated, it is now correctly marked as unsafe starting in the 2024 Edition. This should help ensure that any legacy code which has not already migrated to pre_exec to require an unsafe block.

There are very strict safety requirements for the before_exec closure to satisfy. See the Safety section for more details.

Migration

To make your code compile in both the 2021 and 2024 editions, you will need to make sure that these functions are called only from within unsafe blocks.

⚠ Caution: It is important that you manually inspect the calls to these functions and possibly rewrite your code to satisfy the preconditions of those functions. In particular, set_var and remove_var should not be called if there might be multiple threads running. You may need to elect to use a different mechanism other than environment variables to manage your use case.

The deprecated_safe_2024 lint will automatically modify any use of these functions to be wrapped in an unsafe block so that it can compile on both editions. This lint is part of the rust-2024-compatibility lint group, which will automatically be applied when running cargo fix --edition. To migrate your code to be Rust 2024 Edition compatible, run:

cargo fix --edition

For example, this will change:

fn main() {
    std::env::set_var("FOO", "123");
}

to be:

fn main() {
    // TODO: Audit that the environment access only happens in single-threaded code.
    unsafe { std::env::set_var("FOO", "123") };
}

Just beware that this automatic migration will not be able to verify that these functions are being used correctly. It is still your responsibility to manually review their usage.

Alternatively, you can manually enable the lint to find places these functions are called:

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(deprecated_safe_2024)]
}

Cargo

The following chapters detail changes to Cargo in the 2024 Edition.

Cargo: Rust-version aware resolver

Summary

  • edition = "2024" implies resolver = "3" in Cargo.toml which enables a Rust-version aware dependency resolver.

Details

Since Rust 1.84.0, Cargo has opt-in support for compatibility with package.rust-version to be considered when selecting dependency versions by setting resolver.incompatible-rust-version = "fallback" in .cargo/config.toml.

Starting in Rust 2024, this will be the default. That is, writing edition = "2024" in Cargo.toml will imply resolver = "3" which will imply resolver.incompatible-rust-version = "fallback".

The resolver is a global setting for a workspace, and the setting is ignored in dependencies. The setting is only honored for the top-level package of the workspace. If you are using a virtual workspace, you will still need to explicitly set the resolver field in the [workspace] definition if you want to opt-in to the new resolver.

For more details on how Rust-version aware dependency resolution works, see the Cargo book.

Migration

There are no automated migration tools for updating for the new resolver.

We recommend projects verify against the latest dependencies in CI to catch bugs in dependencies as soon as possible.

Cargo: Table and key name consistency

Summary

  • Several table and key names in Cargo.toml have been removed where there were previously two ways to specify the same thing.
    • Removed [project]; use [package] instead.
    • Removed default_features; use default-features instead.
    • Removed crate_type; use crate-type instead.
    • Removed proc_macro; use proc-macro instead.
    • Removed dev_dependencies; use dev-dependencies instead.
    • Removed build_dependencies; use build-dependencies instead.

Details

Several table and keys names are no longer allowed in the 2024 Edition. There were two ways to specify these tables or keys, and this helps ensure there is only one way to specify them.

Some were due to a change in decisions over time, and some were inadvertent implementation artifacts. In order to avoid confusion, and to enforce a single style for specifying these tables and keys, only one variant is now allowed.

For example:

[dev_dependencies]
rand = { version = "0.8.5", default_features = false }

Should be changed to:

[dev-dependencies]
rand = { version = "0.8.5", default-features = false }

Notice that the underscores were changed to dashes for dev_dependencies and default_features.

Migration

When using cargo fix --edition, Cargo will automatically update your Cargo.toml file to use the preferred table and key names.

If you would prefer to update your Cargo.toml manually, be sure to go through the list above and make sure only the new forms are used.

Cargo: Reject unused inherited default-features

Summary

  • default-features = false is no longer allowed in an inherited workspace dependency if the workspace dependency specifies default-features = true (or does not specify default-features).

Details

Workspace inheritance allows you to specify dependencies in one place (the workspace), and then to refer to those workspace dependencies from within a package. There was an inadvertent interaction with how default-features is specified that is no longer allowed in the 2024 Edition.

Unless the workspace specifies default-features = false, it is no longer allowed to specify default-features = false in an inherited package dependency. For example, with a workspace that specifies:

[workspace.dependencies]
regex = "1.10.4"

The following is now an error:

[package]
name = "foo"
version = "1.0.0"
edition = "2024"

[dependencies]
regex = { workspace = true, default-features = false }  # ERROR

The reason for this change is to avoid confusion when specifying default-features = false when the default feature is already enabled, since it has no effect.

If you want the flexibility of deciding whether or not a dependency enables the default-features of a dependency, be sure to set default-features = false in the workspace definition. Just beware that if you build multiple workspace members at the same time, the features will be unified so that if one member sets default-features = true (which is the default if not explicitly set), the default-features will be enabled for all members using that dependency.

Migration

When using cargo fix --edition, Cargo will automatically update your Cargo.toml file to remove default-features = false in this situation.

If you would prefer to update your Cargo.toml manually, check for any warnings when running a build and remove the corresponding entries. Previous editions should display something like:

warning: /home/project/Cargo.toml: `default-features` is ignored for regex,
since `default-features` was not specified for `workspace.dependencies.regex`,
this could become a hard error in the future

Rustdoc

The following chapters detail changes to Rustdoc in the 2024 Edition.

Rustdoc combined tests

Summary

  • Doctests are now combined into a single binary which should result in a significant performance improvement.

Details

Prior the the 2024 Edition, rustdoc's "test" mode would compile each code block in your documentation as a separate executable. Although this was relatively simple to implement, it resulted in a significant performance burden when there were a large number of documentation tests. Starting with the 2024 Edition, rustdoc will attempt to combine documentation tests into a single binary, significantly reducing the overhead for compiling doctests.

#![allow(unused)]
fn main() {
/// Adds two numbers
///
/// ```
/// assert_eq!(add(1, 1), 2);
/// ```
pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

/// Subtracts two numbers
///
/// ```
/// assert_eq!(subtract(2, 1), 1);
/// ```
pub fn subtract(left: u64, right: u64) -> u64 {
    left - right
}
}

In this example, the two doctests will now be compiled into a single executable. Rustdoc will essentially place each example in a separate function within a single binary. The tests still run in independent processes as they did before, so any global state (like global statics) should still continue to work correctly.1

This change is only available in the 2024 Edition to avoid potential incompatibilities with existing doctests which may not work in a combined executable. However, these incompatibilities are expected to be extremely rare.

1

For more information on the details of how this work, see "Doctests - How were they improved?".

standalone_crate tag

In some situations it is not possible for rustdoc to combine examples into a single executable. Rustdoc will attempt to automatically detect if this is not possible. For example, a test will not be combined with others if it:

  • Uses the compile_fail tag, which indicates that the example should fail to compile.
  • Uses an edition tag, which indicates the edition of the example.2
  • Uses global attributes, like the global_allocator attribute, which could potentially interfere with other tests.
  • Defines any crate-wide attributes (like #![feature(...)]).
  • Defines a macro that uses $crate, because the $crate path will not work correctly.

However, rustdoc is not able to automatically determine all situations where an example cannot be combined with other examples. In these situations, you can add the standalone_crate language tag to indicate that the example should be built as a separate executable. For example:

#![allow(unused)]
fn main() {
//! ```
//! let location = std::panic::Location::caller();
//! assert_eq!(location.line(), 5);
//! ```
}

This is sensitive to the code structure of how the example is compiled and won't work with the "combined" approach because the line numbers will shift depending on how the doctests are combined. In these situations, you can add the standalone_crate tag to force the example to be built separately just as it was in previous editions. E.g.:

#![allow(unused)]
fn main() {
//! ```standalone_crate
//! let location = std::panic::Location::caller();
//! assert_eq!(location.line(), 5);
//! ```
}
2

Note that rustdoc will only combine tests if the entire crate is Edition 2024 or greater. Using the edition2024 tag in older editions will not result in those tests being combined.

Migration

There is no automatic migration to determine which doctests need to be annotated with the standalone_crate tag. It's very unlikely that any given doctest will not work correctly when migrated. We suggest that you update your crate to the 2024 Edition and then run your documentation tests and see if any fail. If one does, you will need to analyze whether it can be rewritten to be compatible with the combined approach, or alternatively, add the standalone_crate tag to retain the previous behavior.

Some things to watch out for and avoid are:

  • Checking the values of std::panic::Location or things that make use of Location. The location of the code is now different since multiple tests are now located in the same test crate.
  • Checking the value of std::any::type_name, which now has a different module path.

Rustdoc nested include! change

Summary

When a doctest is included with include_str!, if that doctest itself also uses include!, include_str!, or include_bytes!, the path is resolved relative to the Markdown file, rather than to the Rust source file.

Details

Prior to the 2024 edition, adding documentation with #[doc=include_str!("path/file.md")] didn't carry span information into any doctests in that file. As a result, if the Markdown file was in a different directory than the source, any paths included had to be specified relative to the source file.

For example, consider a library crate with these files:

  • Cargo.toml
  • README.md
  • src/
    • lib.rs
  • examples/
    • data.bin

Let's say that lib.rs contains this:

#![doc=include_str!("../README.md")]

And assume this README.md file:

```
let _ = include_bytes!("../examples/data.bin");
//                      ^^^ notice this
```

Prior to the 2024 edition, the path in README.md needed to be relative to the lib.rs file. In 2024 and later, it is now relative to README.md itself, so we would update README.md to:

```
let _ = include_bytes!("examples/data.bin");
```

Migration

There is no automatic migration to convert the paths in affected doctests. If one of your doctests is affected, you'll see an error like this after migrating to the new edition when building your tests:

error: couldn't read `../examples/data.bin`: No such file or directory (os error 2)
 --> src/../README.md:2:24
  |
2 | let _ = include_bytes!("../examples/data.bin");
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = note: this error originates in the macro `include_bytes` (in Nightly builds, run with -Z macro-backtrace for more info)
help: there is a file with the same name in a different directory
  |
2 | let _ = include_bytes!("examples/data.bin");
  |                        ~~~~~~~~~~~~~~~~~~~

To migrate your doctests to Rust 2024, update any affected paths to be relative to the file containing the doctests.

Rustfmt

The following chapters detail changes to Rustfmt in the 2024 Edition.

Rustfmt: Style edition

Summary

User can now control which style edition to use with rustfmt.

Details

The default formatting produced by Rustfmt is governed by the rules in the Rust Style Guide.

Additionally, Rustfmt has a formatting stability guarantee that aims to avoid causing noisy formatting churn for users when updating a Rust toolchain. This stability guarantee essentially means that a newer version of Rustfmt cannot modify the successfully formatted output that was produced by a previous version of Rustfmt.

The combination of those two constraints had historically locked both the Style Guide and the default formatting behavior in Rustfmt. This impasse caused various challenges, such as preventing the ability to iterate on style improvements, and requiring Rustfmt to maintain legacy formatting quirks that were obviated long ago (e.g. nested tuple access).

RFC 3338 resolved this impasse by establishing a mechanism for the Rust Style Guide to be aligned to Rust's Edition model wherein the Style Guide could evolve across Editions, and rustfmt would allow users to specify their desired Edition of the Style Guide, referred to as the Style Edition.

In the 2024 Edition, rustfmt now supports the ability for users to control the Style Edition used for formatting. The 2024 Edition of the Style Guide also includes enhancements to the Style Guide which are detailed elsewhere in this Edition Guide.

By default rustfmt will use the same Style Edition as the standard Rust Edition used for parsing, but the Style Edition can also be overridden and configured separately.

There are multiple ways to run rustfmt with the 2024 Style Edition:

With a Cargo.toml file that has edition set to 2024, run:

cargo fmt

Or run rustfmt directly with 2024 for the edition to use the 2024 edition for both parsing and the 2024 edition of the Style Guide:

rustfmt lib.rs --edition 2024

The style edition can also be set in a rustfmt.toml configuration file:

style_edition = "2024"

Which is then used when running rustfmt directly:

rustfmt lib.rs

Alternatively, the style edition can be specified directly from rustfmt options:

rustfmt lib.rs --style-edition 2024

Migration

Running cargo fmt or rustfmt with the 2024 edition or style edition will automatically migrate formatting over to the 2024 style edition formatting.

Projects who have contributors that may utilize their editor's format-on-save features are also strongly encouraged to add a .rustfmt.toml file to their project that includes the corresponding style_edition utilized within their project, or to encourage their users to ensure their local editor format-on-save feature is configured to use that same style_edition.

This is to ensure that the editor format-on-save output is consistent with the output when cargo fmt is manually executed by the developer, or the project's CI process (many editors will run rustfmt directly which by default uses the 2015 edition, whereas cargo fmt uses the edition specified in the Cargo.toml file)

Rustfmt: Assignment operator RHS indentation

Summary

In the 2024 Edition, rustfmt now indents the right-hand side of an assignment operator relative to the last line of the left-hand side, providing a clearer delineation and making it easier to notice the assignment operator.

Details

In Rust 2021 and before, if an assignment operator has a multi-line left-hand side, the indentation of the right-hand side will visually run together with the left-hand side:

impl SomeType {
    fn method(&mut self) {
        self.array[array_index as usize]
            .as_mut()
            .expect("thing must exist")
            .extra_info =
            long_long_long_long_long_long_long_long_long_long_long_long_long_long_long;

        self.array[array_index as usize]
            .as_mut()
            .expect("thing must exist")
            .extra_info = Some(ExtraInfo {
            parent,
            count: count as u16,
            children: children.into_boxed_slice(),
        });
    }
}

In the 2024 Edition, rustfmt now indents the right-hand side relative to the last line of the left-hand side:

impl SomeType {
    fn method(&mut self) {
        self.array[array_index as usize]
            .as_mut()
            .expect("thing must exist")
            .extra_info =
                long_long_long_long_long_long_long_long_long_long_long_long_long_long_long;

        self.array[array_index as usize]
            .as_mut()
            .expect("thing must exist")
            .extra_info = Some(ExtraInfo {
                parent,
                count: count as u16,
                children: children.into_boxed_slice(),
            });
    }
}

Migration

The change can be applied automatically by running cargo fmt or rustfmt with the 2024 Edition. See the Style edition chapter for more information on migrating and how style editions work.

Rustfmt: Combine all delimited exprs as last argument

This feature is not yet implemented. More information may be found in https://github.com/rust-lang/rust/pull/114764.

Summary

Details

Migration

Rustfmt: Single-line where clauses

Summary

When an associated type or method declaration has a single bound in the where clause, it can now sometimes be formatted on one line.

Details

In general, the Rust Style Guide states to wrap where clauses onto subsequent lines, with the keyword where on a line of its own and then the individual clauses indented on subsequent lines.

However, in the 2024 Edition, when writing an associated type declaration or a method declaration (with no body), a short where clause can appear on the same line.

This is particularly useful for generic associated types (GATs), which often need Self: Sized bounds.

In Rust 2021 and before, this would look like:

#![allow(unused)]
fn main() {
trait MyTrait {
    fn new(&self) -> Self
    where
        Self: Sized;

    type Item<'a>: Send
    where
        Self: 'a;
}
}

In the 2024 Edition, rustfmt now produces:

#![allow(unused)]
fn main() {
trait MyTrait {
    fn new(&self) -> Self where Self: Sized;

    type Item<'a>: Send where Self: 'a;
}
}

Migration

The change can be applied automatically by running cargo fmt or rustfmt with the 2024 Edition. See the Style edition chapter for more information on migrating and how style editions work.

Rustfmt: Raw identifier sorting

Summary

rustfmt now properly sorts raw identifiers.

Details

The Rust Style Guide includes rules for sorting that rustfmt applies in various contexts, such as on imports.

Prior to the 2024 Edition, when sorting rustfmt would use the leading r# token instead of the ident which led to unwanted results. For example:

use websocket::client::ClientBuilder;
use websocket::r#async::futures::Stream;
use websocket::result::WebSocketError;

In the 2024 Edition, rustfmt now produces:

use websocket::r#async::futures::Stream;
use websocket::client::ClientBuilder;
use websocket::result::WebSocketError;

Migration

The change can be applied automatically by running cargo fmt or rustfmt with the 2024 Edition. See the Style edition chapter for more information on migrating and how style editions work.

Rustfmt: Version sorting

Summary

rustfmt utilizes a new sorting algorithm.

Details

The Rust Style Guide includes rules for sorting that rustfmt applies in various contexts, such as on imports.

Previous versions of the Style Guide and Rustfmt generally used an "ASCIIbetical" based approach. In the 2024 Edition this is changed to use a version-sort like algorithm that compares Unicode characters lexicographically and provides better results in ASCII digit comparisons.

For example with a given (unsorted) input:

use std::num::{NonZeroU32, NonZeroU16, NonZeroU8, NonZeroU64};
use std::io::{Write, Read, stdout, self};

In the prior Editions, rustfmt would have produced:

use std::io::{self, stdout, Read, Write};
use std::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8};

In the 2024 Edition, rustfmt now produces:

use std::io::{self, Read, Write, stdout};
use std::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64};

Migration

The change can be applied automatically by running cargo fmt or rustfmt with the 2024 Edition. See the Style edition chapter for more information on migrating and how style editions work.

rust_1_83

  • https://blog.rust-lang.org/2024/11/28/Rust-1.83.0.html

Version 1.83.0 (2024-11-28)

Language

Compiler

Refer to Rust's [platform support page][platform-support-doc] for more information on Rust's tiered platform support.

Libraries

Stabilized APIs

These APIs are now stable in const contexts:

Cargo

Rustdoc

Compatibility Notes

rust_1_56_Rust Edition 2021

Rust 2021

Info
RFC#3085
Release version1.56.0

The Rust 2021 Edition contains several changes that bring new capabilities and more consistency to the language, and opens up room for expansion in the future. The following chapters dive into the details of each change, and they include guidance on migrating your existing code.

Additions to the prelude

Summary

  • The TryInto, TryFrom and FromIterator traits are now part of the prelude.
  • This might make calls to trait methods ambiguous which could make some code fail to compile.

Details

The prelude of the standard library is the module containing everything that is automatically imported in every module. It contains commonly used items such as Option, Vec, drop, and Clone.

The Rust compiler prioritizes any manually imported items over those from the prelude, to make sure additions to the prelude will not break any existing code. For example, if you have a crate or module called example containing a pub struct Option;, then use example::*; will make Option unambiguously refer to the one from example; not the one from the standard library.

However, adding a trait to the prelude can break existing code in a subtle way. For example, a call to x.try_into() which comes from a MyTryInto trait might fail to compile if std's TryInto is also imported, because the call to try_into is now ambiguous and could come from either trait. This is the reason we haven't added TryInto to the prelude yet, since there is a lot of code that would break this way.

As a solution, Rust 2021 will use a new prelude. It's identical to the current one, except for three new additions:

The tracking issue can be found here.

Migration

As a part of the 2021 edition a migration lint, rust_2021_prelude_collisions, has been added in order to aid in automatic migration of Rust 2018 codebases to Rust 2021.

In order to migrate your code to be Rust 2021 Edition compatible, run:

cargo fix --edition

The lint detects cases where functions or methods are called that have the same name as the methods defined in one of the new prelude traits. In some cases, it may rewrite your calls in various ways to ensure that you continue to call the same function you did before.

If you'd like to migrate your code manually or better understand what cargo fix is doing, below we've outlined the situations where a migration is needed along with a counter example of when it's not needed.

Migration needed

Conflicting trait methods

When two traits that are in scope have the same method name, it is ambiguous which trait method should be used. For example:

trait MyTrait<A> {
  // This name is the same as the `from_iter` method on the `FromIterator` trait from `std`.  
  fn from_iter(x: Option<A>);
}

impl<T> MyTrait<()> for Vec<T> {
  fn from_iter(_: Option<()>) {}
}

fn main() {
  // Vec<T> implements both `std::iter::FromIterator` and `MyTrait` 
  // If both traits are in scope (as would be the case in Rust 2021),
  // then it becomes ambiguous which `from_iter` method to call
  <Vec<i32>>::from_iter(None);
}

We can fix this by using fully qualified syntax:

fn main() {
  // Now it is clear which trait method we're referring to
  <Vec<i32> as MyTrait<()>>::from_iter(None);
}

Inherent methods on dyn Trait objects

Some users invoke methods on a dyn Trait value where the method name overlaps with a new prelude trait:

#![allow(unused)]
fn main() {
mod submodule {
  pub trait MyTrait {
    // This has the same name as `TryInto::try_into`
    fn try_into(&self) -> Result<u32, ()>;
  }
}

// `MyTrait` isn't in scope here and can only be referred to through the path `submodule::MyTrait`
fn bar(f: Box<dyn submodule::MyTrait>) {
  // If `std::convert::TryInto` is in scope (as would be the case in Rust 2021),
  // then it becomes ambiguous which `try_into` method to call
  f.try_into();
}
}

Unlike with static dispatch methods, calling a trait method on a trait object does not require that the trait be in scope. The code above works as long as there is no trait in scope with a conflicting method name. When the TryInto trait is in scope (which is the case in Rust 2021), this causes an ambiguity. Should the call be to MyTrait::try_into or std::convert::TryInto::try_into?

In these cases, we can fix this by adding an additional dereferences or otherwise clarify the type of the method receiver. This ensures that the dyn Trait method is chosen, versus the methods from the prelude trait. For example, turning f.try_into() above into (&*f).try_into() ensures that we're calling try_into on the dyn MyTrait which can only refer to the MyTrait::try_into method.

No migration needed

Inherent methods

Many types define their own inherent methods with the same name as a trait method. For instance, below the struct MyStruct implements from_iter which shares the same name with the method from the trait FromIterator found in the standard library:

#![allow(unused)]
fn main() {
use std::iter::IntoIterator;

struct MyStruct {
  data: Vec<u32>
}

impl MyStruct {
  // This has the same name as `std::iter::FromIterator::from_iter`
  fn from_iter(iter: impl IntoIterator<Item = u32>) -> Self {
    Self {
      data: iter.into_iter().collect()
    }
  }
}

impl std::iter::FromIterator<u32> for MyStruct {
    fn from_iter<I: IntoIterator<Item = u32>>(iter: I) -> Self {
      Self {
        data: iter.into_iter().collect()
      }
    }
}
}

Inherent methods always take precedent over trait methods so there's no need for any migration.

Implementation Reference

The lint needs to take a couple of factors into account when determining whether or not introducing 2021 Edition to a codebase will cause a name resolution collision (thus breaking the code after changing edition). These factors include:

  • Is the call a fully-qualified call or does it use dot-call method syntax?
    • This will affect how the name is resolved due to auto-reference and auto-dereferencing on method call syntax. Manually dereferencing/referencing will allow specifying priority in the case of dot-call method syntax, while fully-qualified call requires specification of the type and the trait name in the method path (e.g. <Type as Trait>::method)
  • Is this an inherent method or a trait method?
    • Inherent methods that take self will take priority over TryInto::try_into as inherent methods take priority over trait methods, but inherent methods that take &self or &mut self won't take priority due to requiring a auto-reference (while TryInto::try_into does not, as it takes self)
  • Is the origin of this method from core/std? (As the traits can't have a collision with themselves)
  • Does the given type implement the trait it could have a collision against?
  • Is the method being called via dynamic dispatch? (i.e. is the self type dyn Trait)
    • If so, trait imports don't affect resolution, and no migration lint needs to occur

Default Cargo feature resolver

Summary

  • edition = "2021" implies resolver = "2" in Cargo.toml.

Details

Since Rust 1.51.0, Cargo has opt-in support for a new feature resolver which can be activated with resolver = "2" in Cargo.toml.

Starting in Rust 2021, this will be the default. That is, writing edition = "2021" in Cargo.toml will imply resolver = "2".

The resolver is a global setting for a workspace, and the setting is ignored in dependencies. The setting is only honored for the top-level package of the workspace. If you are using a virtual workspace, you will still need to explicitly set the resolver field in the [workspace] definition if you want to opt-in to the new resolver.

The new feature resolver no longer merges all requested features for crates that are depended on in multiple ways. See the announcement of Rust 1.51 for details.

Migration

There are no automated migration tools for updating for the new resolver. For most projects, there are usually few or no changes as a result of updating.

When updating with cargo fix --edition, Cargo will display a report if the new resolver will build dependencies with different features. It may look something like this:

note: Switching to Edition 2021 will enable the use of the version 2 feature resolver in Cargo. This may cause some dependencies to be built with fewer features enabled than previously. More information about the resolver changes may be found at https://doc.rust-lang.org/nightly/edition-guide/rust-2021/default-cargo-resolver.html
When building the following dependencies, the given features will no longer be used:

  bstr v0.2.16: default, lazy_static, regex-automata, unicode
  libz-sys v1.1.3 (as host dependency): libc

This lets you know that certain dependencies will no longer be built with the given features.

Build failures

There may be some circumstances where your project may not build correctly after the change. If a dependency declaration in one package assumes that certain features are enabled in another, and those features are now disabled, it may fail to compile.

For example, let's say we have a dependency like this:

# Cargo.toml

[dependencies]
bstr = { version = "0.2.16", default-features = false }
# ...

And somewhere in our dependency tree, another package has this:

# Another package's Cargo.toml

[build-dependencies]
bstr = "0.2.16"

In our package, we've been using the words_with_breaks method from bstr, which requires bstr's "unicode" feature to be enabled. This has historically worked because Cargo unified the features of bstr between the two packages. However, after updating to Rust 2021, the new resolver will build bstr twice, once with the default features (as a build dependency), and once with no features (as our normal dependency). Since bstr is now being built without the "unicode" feature, the words_with_breaks method doesn't exist, and the build will fail with an error that the method is missing.

The solution here is to ensure that the dependency is declared with the features you are actually using. For example:

[dependencies]
bstr = { version = "0.2.16", default-features = false, features = ["unicode"] }

In some cases, this may be a problem with a third-party dependency that you don't have direct control over. You can consider submitting a patch to that project to try to declare the correct set of features for the problematic dependency. Alternatively, you can add features to any dependency from within your own Cargo.toml file. For example, if the bstr example given above was declared in some third-party dependency, you can just copy the correct dependency declaration into your own project. The features will be unified, as long as they match the unification rules of the new resolver. Those are:

  • Features enabled on platform-specific dependencies for targets not currently being built are ignored.
  • Build-dependencies and proc-macros do not share features with normal dependencies.
  • Dev-dependencies do not activate features unless building a target that needs them (like tests or examples).

A real-world example is using diesel and diesel_migrations. These packages provide database support, and the database is selected using a feature, like this:

[dependencies]
diesel = { version = "1.4.7", features = ["postgres"] }
diesel_migrations = "1.4.0"

The problem is that diesel_migrations has an internal proc-macro which itself depends on diesel, and the proc-macro assumes its own copy of diesel has the same features enabled as the rest of the dependency graph. After updating to the new resolver, it fails to build because now there are two copies of diesel, and the one built for the proc-macro is missing the "postgres" feature.

A solution here is to add diesel as a build-dependency with the required features, for example:

[build-dependencies]
diesel = { version = "1.4.7", features = ["postgres"] }

This causes Cargo to add "postgres" as a feature for host dependencies (proc-macros and build-dependencies). Now, the diesel_migrations proc-macro will get the "postgres" feature enabled, and it will build correctly.

The 2.0 release of diesel (currently in development) does not have this problem as it has been restructured to not have this dependency requirement.

Exploring features

The cargo tree command has had substantial improvements to help with the migration to the new resolver. cargo tree can be used to explore the dependency graph, and to see which features are being enabled, and importantly why they are being enabled.

One option is to use the --duplicates flag (-d for short), which will tell you when a package is being built multiple times. Taking the bstr example from earlier, we might see:

> cargo tree -d
bstr v0.2.16
└── foo v0.1.0 (/MyProjects/foo)

bstr v0.2.16
[build-dependencies]
└── bar v0.1.0
    └── foo v0.1.0 (/MyProjects/foo)

This output tells us that bstr is built twice, and shows the chain of dependencies that led to its inclusion in both cases.

You can print which features each package is using with the -f flag, like this:

cargo tree -f '{p} {f}'

This tells Cargo to change the "format" of the output, where it will print both the package and the enabled features.

You can also use the -e flag to tell it which "edges" to display. For example, cargo tree -e features will show in-between each dependency which features are being added by each dependency. This option becomes more useful with the -i flag which can be used to "invert" the tree. This allows you to see how features flow into a given dependency. For example, let's say the dependency graph is large, and we're not quite sure who is depending on bstr, the following command will show that:

> cargo tree -e features -i bstr
bstr v0.2.16
├── bstr feature "default"
│   [build-dependencies]
│   └── bar v0.1.0
│       └── bar feature "default"
│           └── foo v0.1.0 (/MyProjects/foo)
├── bstr feature "lazy_static"
│   └── bstr feature "unicode"
│       └── bstr feature "default" (*)
├── bstr feature "regex-automata"
│   └── bstr feature "unicode" (*)
├── bstr feature "std"
│   └── bstr feature "default" (*)
└── bstr feature "unicode" (*)

This snippet of output shows that the project foo depends on bar with the "default" feature. Then, bar depends on bstr as a build-dependency with the "default" feature. We can further see that bstr's "default" feature enables "unicode" (among other features).

IntoIterator for arrays

Summary

  • Arrays implement IntoIterator in all editions.
  • Calls to IntoIterator::into_iter are hidden in Rust 2015 and Rust 2018 when using method call syntax (i.e., array.into_iter()). So, array.into_iter() still resolves to (&array).into_iter() as it has before.
  • array.into_iter() changes meaning to be the call to IntoIterator::into_iter in Rust 2021.

Details

Until Rust 1.53, only references to arrays implement IntoIterator. This means you can iterate over &[1, 2, 3] and &mut [1, 2, 3], but not over [1, 2, 3] directly.

for &e in &[1, 2, 3] {} // Ok :)

for e in [1, 2, 3] {} // Error :(

This has been a long-standing issue, but the solution is not as simple as it seems. Just adding the trait implementation would break existing code. array.into_iter() already compiles today because that implicitly calls (&array).into_iter() due to how method call syntax works. Adding the trait implementation would change the meaning.

Usually this type of breakage (adding a trait implementation) is categorized as 'minor' and acceptable. But in this case there is too much code that would be broken by it.

It has been suggested many times to "only implement IntoIterator for arrays in Rust 2021". However, this is simply not possible. You can't have a trait implementation exist in one edition and not in another, since editions can be mixed.

Instead, the trait implementation was added in all editions (starting in Rust 1.53.0) but with a small hack to avoid breakage until Rust 2021. In Rust 2015 and 2018 code, the compiler will still resolve array.into_iter() to (&array).into_iter() like before, as if the trait implementation does not exist. This only applies to the .into_iter() method call syntax. It does not affect any other syntax such as for e in [1, 2, 3], iter.zip([1, 2, 3]) or IntoIterator::into_iter([1, 2, 3]). Those will start to work in all editions.

While it's a shame that this required a small hack to avoid breakage, this solution keeps the difference between the editions to an absolute minimum.

Migration

A lint, array_into_iter, gets triggered whenever there is some call to into_iter() that will change meaning in Rust 2021. The array_into_iter lint has already been a warning by default on all editions since the 1.41 release (with several enhancements made in 1.55). If your code is already warning free, then it should already be ready to go for Rust 2021!

You can automatically migrate your code to be Rust 2021 Edition compatible or ensure it is already compatible by running:

cargo fix --edition

Because the difference between editions is small, the migration to Rust 2021 is fairly straight-forward.

For method calls of into_iter on arrays, the elements being implemented will change from references to owned values.

For example:

fn main() {
  let array = [1u8, 2, 3];
  for x in array.into_iter() {
    // x is a `&u8` in Rust 2015 and Rust 2018
    // x is a `u8` in Rust 2021
  }
}

The most straightforward way to migrate in Rust 2021, is by keeping the exact behavior from previous editions by calling iter() which also iterates over owned arrays by reference:

fn main() {
  let array = [1u8, 2, 3];
  for x in array.iter() { // <- This line changed
    // x is a `&u8` in all editions
  }
}

Optional migration

If you are using fully qualified method syntax (i.e., IntoIterator::into_iter(array)) in a previous edition, this can be upgraded to method call syntax (i.e., array.into_iter()).

Disjoint capture in closures

Summary

  • || a.x + 1 now captures only a.x instead of a.
  • This can cause things to be dropped at different times or affect whether closures implement traits like Send or Clone.
    • If possible changes are detected, cargo fix will insert statements like let _ = &a to force a closure to capture the entire variable.

Details

Closures automatically capture anything that you refer to from within their body. For example, || a + 1 automatically captures a reference to a from the surrounding context.

In Rust 2018 and before, closures capture entire variables, even if the closure only uses one field. For example, || a.x + 1 captures a reference to a and not just a.x. Capturing a in its entirety prevents mutation or moves from other fields of a, so that code like this does not compile:

let a = SomeStruct::new();
drop(a.x); // Move out of one field of the struct
println!("{}", a.y); // Ok: Still use another field of the struct
let c = || println!("{}", a.y); // Error: Tries to capture all of `a`
c();

Starting in Rust 2021, closures captures are more precise. Typically they will only capture the fields they use (in some cases, they might capture more than just what they use, see the Rust reference for full details). Therefore, the above example will compile fine in Rust 2021.

Disjoint capture was proposed as part of RFC 2229 and the RFC contains details about the motivation.

Migration

As a part of the 2021 edition a migration lint, rust_2021_incompatible_closure_captures, has been added in order to aid in automatic migration of Rust 2018 codebases to Rust 2021.

In order to migrate your code to be Rust 2021 Edition compatible, run:

cargo fix --edition

Below is an examination of how to manually migrate code to use closure captures that are compatible with Rust 2021 should the automatic migration fail or you would like to better understand how the migration works.

Changing the variables captured by a closure can cause programs to change behavior or to stop compiling in two cases:

  • changes to drop order, or when destructors run (details);
  • changes to which traits a closure implements (details).

Whenever any of the scenarios below are detected, cargo fix will insert a "dummy let" into your closure to force it to capture the entire variable:

#![allow(unused)]
fn main() {
let x = (vec![22], vec![23]);
let c = move || {
    // "Dummy let" that forces `x` to be captured in its entirety
    let _ = &x;

    // Otherwise, only `x.0` would be captured here
    println!("{:?}", x.0);
};
}

This is a conservative analysis: in many cases, these dummy lets can be safely removed and your program will work fine.

Wild Card Patterns

Closures now only capture data that needs to be read, which means the following closures will not capture x:

#![allow(unused)]
fn main() {
let x = 10;
let c = || {
    let _ = x; // no-op
};

let c = || match x {
    _ => println!("Hello World!")
};
}

The let _ = x statement here is a no-op, since the _ pattern completely ignores the right-hand side, and x is a reference to a place in memory (in this case, a variable).

This change by itself (capturing fewer values) doesn't trigger any suggestions, but it may do so in conjunction with the "drop order" change below.

Subtle: There are other similar expressions, such as the "dummy lets" let _ = &x that we insert, which are not no-ops. This is because the right-hand side (&x) is not a reference to a place in memory, but rather an expression that must first be evaluated (and whose result is then discarded).

Drop Order

When a closure takes ownership of a value from a variable t, that value is then dropped when the closure is dropped, and not when the variable t goes out of scope:

#![allow(unused)]
fn main() {
fn move_value<T>(_: T){}
{
    let t = (vec![0], vec![0]);

    {
        let c = || move_value(t); // t is moved here
    } // c is dropped, which drops the tuple `t` as well
} // t goes out of scope here
}

The above code will run the same in both Rust 2018 and Rust 2021. However, in cases where the closure only takes ownership of part of a variable, there can be differences:

#![allow(unused)]
fn main() {
fn move_value<T>(_: T){}
{
    let t = (vec![0], vec![0]);

    {
        let c = || {
            // In Rust 2018, captures all of `t`.
            // In Rust 2021, captures only `t.0`
            move_value(t.0);
        };

        // In Rust 2018, `c` (and `t`) are both dropped when we
        // exit this block.
        //
        // In Rust 2021, `c` and `t.0` are both dropped when we
        // exit this block.
    }

// In Rust 2018, the value from `t` has been moved and is
// not dropped.
//
// In Rust 2021, the value from `t.0` has been moved, but `t.1`
// remains, so it will be dropped here.
}
}

In most cases, dropping values at different times just affects when memory is freed and is not important. However, some Drop impls (aka, destructors) have side-effects, and changing the drop order in those cases can alter the semantics of your program. In such cases, the compiler will suggest inserting a dummy let to force the entire variable to be captured.

Trait implementations

Closures automatically implement the following traits based on what values they capture:

In Rust 2021, since different values are being captured, this can affect what traits a closure will implement. The migration lints test each closure to see whether it would have implemented a given trait before and whether it still implements it now; if they find that a trait used to be implemented but no longer is, then "dummy lets" are inserted.

For instance, a common way to allow passing around raw pointers between threads is to wrap them in a struct and then implement Send/Sync auto trait for the wrapper. The closure that is passed to thread::spawn uses the specific fields within the wrapper but the entire wrapper is captured regardless. Since the wrapper is Send/Sync, the code is considered safe and therefore compiles successfully.

With disjoint captures, only the specific field mentioned in the closure gets captured, which wasn't originally Send/Sync defeating the purpose of the wrapper.

#![allow(unused)]
fn main() {
use std::thread;

struct Ptr(*mut i32);
unsafe impl Send for Ptr {}


let mut x = 5;
let px = Ptr(&mut x as *mut i32);

let c = thread::spawn(move || {
    unsafe {
        *(px.0) += 10;
    }
}); // Closure captured px.0 which is not Send
}

Panic macro consistency

Summary

  • panic!(..) now always uses format_args!(..), just like println!().
  • panic!("{") is no longer accepted, without escaping the { as {{.
  • panic!(x) is no longer accepted if x is not a string literal.
    • Use std::panic::panic_any(x) to panic with a non-string payload.
    • Or use panic!("{}", x) to use x's Display implementation.
  • The same applies to assert!(expr, ..).

Details

The panic!() macro is one of Rust's most well known macros. However, it has some subtle surprises that we can't just change due to backwards compatibility.

// Rust 2018
panic!("{}", 1); // Ok, panics with the message "1"
panic!("{}"); // Ok, panics with the message "{}"

The panic!() macro only uses string formatting when it's invoked with more than one argument. When invoked with a single argument, it doesn't even look at that argument.

// Rust 2018
let a = "{";
println!(a); // Error: First argument must be a format string literal
panic!(a); // Ok: The panic macro doesn't care

It even accepts non-strings such as panic!(123), which is uncommon and rarely useful since it produces a surprisingly unhelpful message: panicked at 'Box<Any>'.

This will especially be a problem once implicit format arguments are stabilized. That feature will make println!("hello {name}") a short-hand for println!("hello {}", name). However, panic!("hello {name}") would not work as expected, since panic!() doesn't process a single argument as format string.

To avoid that confusing situation, Rust 2021 features a more consistent panic!() macro. The new panic!() macro will no longer accept arbitrary expressions as the only argument. It will, just like println!(), always process the first argument as format string. Since panic!() will no longer accept arbitrary payloads, panic_any() will be the only way to panic with something other than a formatted string.

// Rust 2021
panic!("{}", 1); // Ok, panics with the message "1"
panic!("{}"); // Error, missing argument
panic!(a); // Error, must be a string literal

In addition, core::panic!() and std::panic!() will be identical in Rust 2021. Currently, there are some historical differences between those two, which can be noticeable when switching #![no_std] on or off.

Migration

A lint, non_fmt_panics, gets triggered whenever there is some call to panic that uses some deprecated behavior that will error in Rust 2021. The non_fmt_panics lint has already been a warning by default on all editions since the 1.50 release (with several enhancements made in later releases). If your code is already warning free, then it should already be ready to go for Rust 2021!

You can automatically migrate your code to be Rust 2021 Edition compatible or ensure it is already compatible by running:

cargo fix --edition

Should you choose or need to manually migrate, you'll need to update all panic invocations to either use the same formatting as println or use std::panic::panic_any to panic with non-string data.

For example, in the case of panic!(MyStruct), you'll need to convert to using std::panic::panic_any (note that this is a function not a macro): std::panic::panic_any(MyStruct).

In the case of panic messages that include curly braces but the wrong number of arguments (e.g., panic!("Some curlies: {}")), you can panic with the string literal by either using the same syntax as println! (i.e., panic!("{}", "Some curlies: {}")) or by escaping the curly braces (i.e., panic!("Some curlies: {{}}")).

Reserved syntax

Summary

  • any_identifier#, any_identifier"...", any_identifier'...', and 'any_identifier# are now reserved syntax, and no longer tokenize.
  • This is mostly relevant to macros. E.g. quote!{ #a#b } is no longer accepted.
  • It doesn't treat keywords specially, so e.g. match"..." {} is no longer accepted.
  • Insert whitespace between the identifier and the subsequent #, ", or ' to avoid errors.
  • Edition migrations will help you insert whitespace in such cases.

Details

To make space for new syntax in the future, we've decided to reserve syntax for prefixed identifiers, literals, and lifetimes: prefix#identifier, prefix"string", prefix'c', prefix#123, and 'prefix#, where prefix can be any identifier. (Except those prefixes that already have a meaning, such as b'...' (byte chars) and r"..." (raw strings).)

This provides syntax we can expand into in the future without requiring an edition boundary. We may use this for temporary syntax until the next edition, or for permanent syntax if appropriate.

Without an edition, this would be a breaking change, since macros can currently accept syntax such as hello"world", which they will see as two separate tokens: hello and "world". The (automatic) fix is simple though: just insert a space: hello "world". Likewise, prefix#ident should become prefix #ident. Edition migrations will help with this fix.

Other than turning these into a tokenization error, the RFC does not attach a meaning to any prefix yet. Assigning meaning to specific prefixes is left to future proposals, which will now—thanks to reserving these prefixes—not be breaking changes.

Some new prefixes you might potentially see in the future (though we haven't committed to any of them yet):

  • k#keyword to allow writing keywords that don't exist yet in the current edition. For example, while async is not a keyword in edition 2015, this prefix would've allowed us to accept k#async in edition 2015 without having to wait for edition 2018 to reserve async as a keyword.

  • f"" as a short-hand for a format string. For example, f"hello {name}" as a short-hand for the equivalent format!() invocation.

  • s"" for String literals.

Migration

As a part of the 2021 edition a migration lint, rust_2021_prefixes_incompatible_syntax, has been added in order to aid in automatic migration of Rust 2018 codebases to Rust 2021.

In order to migrate your code to be Rust 2021 Edition compatible, run:

cargo fix --edition

Should you want or need to manually migrate your code, migration is fairly straight-forward.

Let's say you have a macro that is defined like so:

#![allow(unused)]
fn main() {
macro_rules! my_macro {
    ($a:tt $b:tt) => {};
}
}

In Rust 2015 and 2018 it's legal for this macro to be called like so with no space between the first token tree and the second:

my_macro!(z"hey");

This z prefix is no longer allowed in Rust 2021, so in order to call this macro, you must add a space after the prefix like so:

my_macro!(z "hey");

Raw lifetimes

Summary

  • 'r#ident_or_keyword is now allowed as a lifetime, which allows using keywords such as 'r#fn.

Details

Raw lifetimes are introduced in the 2021 edition to support the ability to migrate to newer editions that introduce new keywords. This is analogous to raw identifiers which provide the same functionality for identifiers. For example, the 2024 edition introduced the gen keyword. Since lifetimes cannot be keywords, this would cause code that use a lifetime 'gen to fail to compile. Raw lifetimes allow the migration lint to modify those lifetimes to 'r#gen which do allow keywords.

In editions prior to 2021, raw lifetimes are parsed as separate tokens. For example 'r#foo is parsed as three tokens: 'r, #, and foo.

Migration

As a part of the 2021 edition a migration lint, rust_2021_prefixes_incompatible_syntax, has been added in order to aid in automatic migration of Rust 2018 codebases to Rust 2021.

In order to migrate your code to be Rust 2021 Edition compatible, run:

cargo fix --edition

Should you want or need to manually migrate your code, migration is fairly straight-forward.

Let's say you have a macro that is defined like so:

#![allow(unused)]
fn main() {
macro_rules! my_macro {
    ($a:tt $b:tt $c:tt) => {};
}
}

In Rust 2015 and 2018 it's legal for this macro to be called like so with no space between the tokens:

my_macro!('r#foo);

In the 2021 edition, this is now parsed as a single token. In order to call this macro, you must add a space before the identifier like so:

my_macro!('r# foo);

Warnings promoted to errors

Summary

  • Code that triggered the bare_trait_objects and ellipsis_inclusive_range_patterns lints will error in Rust 2021.

Details

Two existing lints are becoming hard errors in Rust 2021, but these lints will remain warnings in older editions.

bare_trait_objects:

The use of the dyn keyword to identify trait objects will be mandatory in Rust 2021.

For example, the following code which does not include the dyn keyword in &MyTrait will produce an error instead of just a lint in Rust 2021:

#![allow(unused)]
fn main() {
pub trait MyTrait {}

pub fn my_function(_trait_object: &MyTrait) { // should be `&dyn MyTrait`
  unimplemented!()
}
}

ellipsis_inclusive_range_patterns:

The deprecated ... syntax for inclusive range patterns (i.e., ranges where the end value is included in the range) is no longer accepted in Rust 2021. It has been superseded by ..=, which is consistent with expressions.

For example, the following code which uses ... in a pattern will produce an error instead of just a lint in Rust 2021:

#![allow(unused)]
fn main() {
pub fn less_or_eq_to_100(n: u8) -> bool {
  matches!(n, 0...100) // should be `0..=100`
}
}

Migrations

If your Rust 2015 or 2018 code does not produce any warnings for bare_trait_objects or ellipsis_inclusive_range_patterns and you've not allowed these lints through the use of #![allow()] or some other mechanism, then there's no need to migrate.

To automatically migrate any crate that uses ... in patterns or does not use dyn with trait objects, you can run cargo fix --edition.

Or patterns in macro-rules

Summary

  • How patterns work in macro_rules macros changes slightly:
    • $_:pat in macro_rules now matches usage of | too: e.g. A | B.
    • The new $_:pat_param behaves like $_:pat did before; it does not match (top level) |.
    • $_:pat_param is available in all editions.

Details

Starting in Rust 1.53.0, patterns are extended to support | nested anywhere in the pattern. This enables you to write Some(1 | 2) instead of Some(1) | Some(2). Since this was simply not allowed before, this is not a breaking change.

However, this change also affects macro_rules macros. Such macros can accept patterns using the :pat fragment specifier. Currently, :pat does not match top level |, since before Rust 1.53, not all patterns (at all nested levels) could contain a |. Macros that accept patterns like A | B, such as matches!() use something like $($_:pat)|+.

Because this would potentially break existing macros, the meaning of :pat did not change in Rust 1.53.0 to include |. Instead, that change happens in Rust 2021. In the new edition, the :pat fragment specifier will match A | B.

$_:pat fragments in Rust 2021 cannot be followed by an explicit |. Since there are times that one still wishes to match pattern fragments followed by a |, the fragment specified :pat_param has been added to retain the older behavior.

It's important to remember that editions are per crate, so the only relevant edition is the edition of the crate where the macro is defined. The edition of the crate where the macro is used does not change how the macro works.

Migration

A lint, rust_2021_incompatible_or_patterns, gets triggered whenever there is a use $_:pat which will change meaning in Rust 2021.

You can automatically migrate your code to be Rust 2021 Edition compatible or ensure it is already compatible by running:

cargo fix --edition

If you have a macro which relies on $_:pat not matching the top level use of | in patterns, you'll need to change each occurrence of $_:pat to $_:pat_param.

For example:

#![allow(unused)]
fn main() {
macro_rules! my_macro { 
	($x:pat | $y:pat) => {
		// TODO: implementation
	} 
}

// This macro works in Rust 2018 since `$x:pat` does not match against `|`:
my_macro!(1 | 2);

// In Rust 2021 however, the `$_:pat` fragment matches `|` and is not allowed
// to be followed by a `|`. To make sure this macro still works in Rust 2021
// change the macro to the following:
macro_rules! my_macro { 
	($x:pat_param | $y:pat) => { // <- this line is different
		// TODO: implementation
	} 
}
}

C-string literals

Summary

  • Literals of the form c"foo" or cr"foo" represent a string of type &core::ffi::CStr.

Details

Starting with Rust 1.77, C-strings can be written using C-string literal syntax with the c or cr prefix.

Previously, it was challenging to properly produce a valid string literal that could interoperate with C APIs which terminate with a NUL byte. The cstr crate was a popular solution, but that required compiling a proc-macro which was quite expensive. Now, C-strings can be written directly using literal syntax notation, which will generate a value of type &core::ffi::CStr which is automatically terminated with a NUL byte.

#![allow(unused)]
fn main() {
use core::ffi::CStr;

assert_eq!(c"hello", CStr::from_bytes_with_nul(b"hello\0").unwrap());
assert_eq!(
    c"byte escapes \xff work",
    CStr::from_bytes_with_nul(b"byte escapes \xff work\0").unwrap()
);
assert_eq!(
    c"unicode escapes \u{00E6} work",
    CStr::from_bytes_with_nul(b"unicode escapes \xc3\xa6 work\0").unwrap()
);
assert_eq!(
    c"unicode characters αβγ encoded as UTF-8",
    CStr::from_bytes_with_nul(
        b"unicode characters \xce\xb1\xce\xb2\xce\xb3 encoded as UTF-8\0"
    )
    .unwrap()
);
assert_eq!(
    c"strings can continue \
        on multiple lines",
    CStr::from_bytes_with_nul(b"strings can continue on multiple lines\0").unwrap()
);
}

C-strings do not allow interior NUL bytes (such as with a \0 escape).

Similar to regular strings, C-strings also support "raw" syntax with the cr prefix. These raw C-strings do not process backslash escapes which can make it easier to write strings that contain backslashes. Double-quotes can be included by surrounding the quotes with the # character. Multiple # characters can be used to avoid ambiguity with internal "# sequences.

#![allow(unused)]
fn main() {
assert_eq!(cr"foo", c"foo");
// Number signs can be used to embed interior double quotes.
assert_eq!(cr#""foo""#, c"\"foo\"");
// This requires two #.
assert_eq!(cr##""foo"#"##, c"\"foo\"#");
// Escapes are not processed.
assert_eq!(cr"C:\foo", c"C:\\foo");
}

See The Reference for more details.

Migration

Migration is only necessary for macros which may have been assuming a sequence of tokens that looks similar to c"…" or cr"…", which previous to the 2021 edition would tokenize as two separate tokens, but in 2021 appears as a single token.

As part of the syntax reservation for the 2021 edition, any macro input which may run into this issue should issue a warning from the rust_2021_prefixes_incompatible_syntax migration lint. See that chapter for more detail.

rust_1_31_Rust Edition 2018

link


Rust 2018

Info
RFC#2052, which also proposed the Edition system
Release version1.31.0

The edition system was created for the release of Rust 2018. The release of the Rust 2018 edition coincided with a number of other features all coordinated around the theme of productivity. The majority of those features were backwards compatible and are now available on all editions; however, some of those changes required the edition mechanism (most notably the module system changes).

Path and module system changes

Minimum Rust version: 1.31

Summary

  • Paths in use declarations now work the same as other paths.
  • Paths starting with :: must now be followed with an external crate.
  • Paths in pub(in path) visibility modifiers must now start with crate, self, or super.

Motivation

The module system is often one of the hardest things for people new to Rust. Everyone has their own things that take time to master, of course, but there's a root cause for why it's so confusing to many: while there are simple and consistent rules defining the module system, their consequences can feel inconsistent, counterintuitive and mysterious.

As such, the 2018 edition of Rust introduces a few new module system features, but they end up simplifying the module system, to make it more clear as to what is going on.

Here's a brief summary:

  • extern crate is no longer needed in 99% of circumstances.
  • The crate keyword refers to the current crate.
  • Paths may start with a crate name, even within submodules.
  • Paths starting with :: must reference an external crate.
  • A foo.rs and foo/ subdirectory may coexist; mod.rs is no longer needed when placing submodules in a subdirectory.
  • Paths in use declarations work the same as other paths.

These may seem like arbitrary new rules when put this way, but the mental model is now significantly simplified overall. Read on for more details!

More details

Let's talk about each new feature in turn.

No more extern crate

This one is quite straightforward: you no longer need to write extern crate to import a crate into your project. Before:

// Rust 2015

extern crate futures;

mod submodule {
    use futures::Future;
}

After:

// Rust 2018

mod submodule {
    use futures::Future;
}

Now, to add a new crate to your project, you can add it to your Cargo.toml, and then there is no step two. If you're not using Cargo, you already had to pass --extern flags to give rustc the location of external crates, so you'd just keep doing what you were doing there as well.

An exception

There's one exception to this rule, and that's the "sysroot" crates. These are the crates distributed with Rust itself.

Usually these are only needed in very specialized situations. Starting in 1.41, rustc accepts the --extern=CRATE_NAME flag which automatically adds the given crate name in a way similar to extern crate. Build tools may use this to inject sysroot crates into the crate's prelude. Cargo does not have a general way to express this, though it uses it for proc_macro crates.

Some examples of needing to explicitly import sysroot crates are:

  • std: Usually this is not necessary, because std is automatically imported unless the crate is marked with #![no_std].
  • core: Usually this is not necessary, because core is automatically imported, unless the crate is marked with #![no_core]. For example, some of the internal crates used by the standard library itself need this.
  • proc_macro: This is automatically imported by Cargo if it is a proc-macro crate starting in 1.42. extern crate proc_macro; would be needed if you want to support older releases, or if using another build tool that does not pass the appropriate --extern flags to rustc.
  • alloc: Items in the alloc crate are usually accessed via re-exports in the std crate. If you are working with a no_std crate that supports allocation, then you may need to explicitly import alloc.
  • test: This is only available on the nightly channel, and is usually only used for the unstable benchmark support.

Macros

One other use for extern crate was to import macros; that's no longer needed. Macros may be imported with use like any other item. For example, the following use of extern crate:

#[macro_use]
extern crate bar;

fn main() {
    baz!();
}

Can be changed to something like the following:

use bar::baz;

fn main() {
    baz!();
}

Renaming crates

If you've been using as to rename your crate like this:

extern crate futures as f;

use f::Future;

then removing the extern crate line on its own won't work. You'll need to do this:

use futures as f;

use self::f::Future;

This change will need to happen in any module that uses f.

The crate keyword refers to the current crate

In use declarations and in other code, you can refer to the root of the current crate with the crate:: prefix. For instance, crate::foo::bar will always refer to the name bar inside the module foo, from anywhere else in the same crate.

The prefix :: previously referred to either the crate root or an external crate; it now unambiguously refers to an external crate. For instance, ::foo::bar always refers to the name bar inside the external crate foo.

Extern crate paths

Previously, using an external crate in a module without a use import required a leading :: on the path.

// Rust 2015

extern crate chrono;

fn foo() {
    // this works in the crate root
    let x = chrono::Utc::now();
}

mod submodule {
    fn function() {
        // but in a submodule it requires a leading :: if not imported with `use`
        let x = ::chrono::Utc::now();
    }
}

Now, extern crate names are in scope in the entire crate, including submodules.

// Rust 2018

fn foo() {
    // this works in the crate root
    let x = chrono::Utc::now();
}

mod submodule {
    fn function() {
        // crates may be referenced directly, even in submodules
        let x = chrono::Utc::now();
    }
}

If you have a local module or item with the same name as an external crate, a path beginning with that name will be taken to refer to the local module or item. To explicitly refer to the external crate, use the ::name form.

No more mod.rs

In Rust 2015, if you have a submodule:

// This `mod` declaration looks for the `foo` module in
// `foo.rs` or `foo/mod.rs`.
mod foo;

It can live in foo.rs or foo/mod.rs. If it has submodules of its own, it must be foo/mod.rs. So a bar submodule of foo would live at foo/bar.rs.

In Rust 2018 the restriction that a module with submodules must be named mod.rs is lifted. foo.rs can just be foo.rs, and the submodule is still foo/bar.rs. This eliminates the special name, and if you have a bunch of files open in your editor, you can clearly see their names, instead of having a bunch of tabs named mod.rs.

Rust 2015 Rust 2018
.
├── lib.rs
└── foo/
    ├── mod.rs
    └── bar.rs
.
├── lib.rs
├── foo.rs
└── foo/
    └── bar.rs

use paths

Minimum Rust version: 1.32

Rust 2018 simplifies and unifies path handling compared to Rust 2015. In Rust 2015, paths work differently in use declarations than they do elsewhere. In particular, paths in use declarations would always start from the crate root, while paths in other code implicitly started from the current scope. Those differences didn't have any effect in the top-level module, which meant that everything would seem straightforward until working on a project large enough to have submodules.

In Rust 2018, paths in use declarations and in other code work the same way, both in the top-level module and in any submodule. You can use a relative path from the current scope, a path starting from an external crate name, or a path starting with ::, crate, super, or self.

Code that looked like this:

// Rust 2015

extern crate futures;

use futures::Future;

mod foo {
    pub struct Bar;
}

use foo::Bar;

fn my_poll() -> futures::Poll { ... }

enum SomeEnum {
    V1(usize),
    V2(String),
}

fn func() {
    let five = std::sync::Arc::new(5);
    use SomeEnum::*;
    match ... {
        V1(i) => { ... }
        V2(s) => { ... }
    }
}

will look exactly the same in Rust 2018, except that you can delete the extern crate line:

// Rust 2018

use futures::Future;

mod foo {
    pub struct Bar;
}

use foo::Bar;

fn my_poll() -> futures::Poll { ... }

enum SomeEnum {
    V1(usize),
    V2(String),
}

fn func() {
    let five = std::sync::Arc::new(5);
    use SomeEnum::*;
    match ... {
        V1(i) => { ... }
        V2(s) => { ... }
    }
}

The same code will also work completely unmodified in a submodule:

// Rust 2018

mod submodule {
    use futures::Future;

    mod foo {
        pub struct Bar;
    }

    use foo::Bar;

    fn my_poll() -> futures::Poll { ... }

    enum SomeEnum {
        V1(usize),
        V2(String),
    }

    fn func() {
        let five = std::sync::Arc::new(5);
        use SomeEnum::*;
        match ... {
            V1(i) => { ... }
            V2(s) => { ... }
        }
    }
}

This makes it easy to move code around in a project, and avoids introducing additional complexity to multi-module projects.

Anonymous trait function parameters deprecated

Minimum Rust version: 1.31

Summary

Details

In accordance with RFC #1685, parameters in trait method declarations are no longer allowed to be anonymous.

For example, in the 2015 edition, this was allowed:

#![allow(unused)]
fn main() {
trait Foo {
    fn foo(&self, u8);
}
}

In the 2018 edition, all parameters must be given an argument name (even if it's just _):

#![allow(unused)]
fn main() {
trait Foo {
    fn foo(&self, baz: u8);
}
}

New keywords

Minimum Rust version: 1.27

Summary

Motivation

dyn Trait for trait objects

The dyn Trait feature is the new syntax for using trait objects. In short:

  • Box<Trait> becomes Box<dyn Trait>
  • &Trait and &mut Trait become &dyn Trait and &mut dyn Trait

And so on. In code:

#![allow(unused)]
fn main() {
trait Trait {}

impl Trait for i32 {}

// old
fn function1() -> Box<Trait> {
unimplemented!()
}

// new
fn function2() -> Box<dyn Trait> {
unimplemented!()
}
}

That's it!

Why?

Using just the trait name for trait objects turned out to be a bad decision. The current syntax is often ambiguous and confusing, even to veterans, and favors a feature that is not more frequently used than its alternatives, is sometimes slower, and often cannot be used at all when its alternatives can.

Furthermore, with impl Trait arriving, "impl Trait vs dyn Trait" is much more symmetric, and therefore a bit nicer, than "impl Trait vs Trait". impl Trait is explained here.

In the new edition, you should therefore prefer dyn Trait to just Trait where you need a trait object.

async and await

These keywords are reserved to implement the async-await feature of Rust, which was ultimately released to stable in 1.39.0.

try keyword

The try keyword is reserved for use in try blocks, which have not (as of this writing) been stabilized (tracking issue)

Method dispatch for raw pointers to inference variables

Summary

Details

See Rust issue #46906 for details.

Cargo changes

Summary

  • If there is a target definition in a Cargo.toml manifest, it no longer automatically disables automatic discovery of other targets.
  • Target paths of the form src/{target_name}.rs are no longer inferred for targets where the path field is not set.
  • cargo install for the current directory is no longer allowed, you must specify cargo install --path . to install the current package.

Rust 2015

Rust 2015 has a theme of "stability". It commenced with the release of 1.0, and is the "default edition". The edition system was conceived in late 2017, but Rust 1.0 was released in May of 2015. As such, 2015 is the edition that you get when you don't specify any particular edition, for backwards compatibility reasons.

"Stability" is the theme of Rust 2015 because 1.0 marked a huge change in Rust development. Previous to Rust 1.0, Rust was changing on a daily basis. This made it very difficult to write large software in Rust, and made it difficult to learn. With the release of Rust 1.0 and Rust 2015, we committed to backwards compatibility, ensuring a solid foundation for people to build projects on top of.

Since it's the default edition, there's no way to port your code to Rust 2015; it just is. You'll be transitioning away from 2015, but never really to 2015. As such, there's not much else to say about it!

rust_rfcs

Rust Parallelism&Concurrency

link







외부 자료



Async Rust여기에 정리중|🔝|

Green Thread그린쓰레드 이해하기|🔝|


Rc, Arc 그림, 표로 잘 정리됨.|🔝|


1 Hour Dive into Asynchronous Rust | Ardan Labs|🔝|


OpenTeleMetry(Rust)|🔝|

https://opentelemetry.io/docs/languages/rust/getting-started/


Tokio-Console : It's like "htop" for async|🔝|

cargo install tokio-console


What is Pinning?|🔝|

  • Rust's memory model strictly ensures that references must still exist, won't move, and won't be bropped while still in use.
    • 러스트의 메모리 모델은 참조가 여전히 존재해야 하고, 움직이지 않아야 하며, 사용 중에도 끊어지지 않도록 엄격하게 보장합니다.
  • That's great for avoiding common memory bugs.
    • 일반적인 메모리 버그를 방지하는 데 좋습니다.
  • It's tricky in a highly asynchronous environment, tasks may depend upon other tasks - which typically move around quite a bit.
    • 매우 비동기적인 환경에서는 작업이 까다롭기 때문에 작업은 다른 작업에 따라 달라질 수 있습니다. 이 작업은 일반적으로 상당히 많이 움직입니다.
  • Pinning lets you tell Rust that a variable needs to stick around - in the same place - untile you unpin it.
    • Pinning피닝을 사용하면 Rust에게 변수를 풀 때까지 같은 위치에 있어야 한다는 것을 알 수 있습니다.
      • A stream that relies upon another stream will typically pin its access to the previous stream.
        • 다른 스트림에 의존하는 스트림은 일반적으로 이전 스트림에 대한 액세스를 고정합니다.
      • A select operation may need to pin entries for the same reason.
        • 선택 작업에서 동일한 이유로 항목을 고정해야 할 수도 있습니다.
      • Asynchronously calling yourself-recursion - requires pinning the iterations.
        • 비동기적으로 자신을 호출하려면 반복을 고정해야 합니다.

출처 : 59min30sec__1 Hour Dive into Asynchronous Rust


OpenTelemetry|🔝|

# Cargo.toml
[dependencies]
opentelemetry = "0.22"
opentelemetry_sdk = "0.22"
opentelemetry-stdout = { version = "0.3", features = ["trace"] }
use opentelemetry::{
    global,
    sdk::trace::TracerProvider,
    trace::{Tracer, TracerProvider as _},
};

fn main() {
    // Create a new trace pipeline that prints to stdout
    let provider = TracerProvider::builder()
        .with_simple_exporter(opentelemetry_stdout::SpanExporter::default())
        .build();
    let tracer = provider.tracer("readme_example");

    tracer.in_span("doing_work", |cx| {
        // Traced app logic here...
    });

    // Shutdown trace pipeline
    global::shutdown_tracer_provider();
}

use std::{thread, time::Duration};

use opentelemetry::{
    global,
    trace::{TraceContextExt, Tracer},
    Key, KeyValue,
};
use uptrace::UptraceBuilder;

#[tokio::main]
async fn main() {
    UptraceBuilder::new()
        //.with_dsn("")
        .with_service_name("myservice")
        .with_service_version("1.0.0")
        .with_deployment_environment("testing")
        .configure_opentelemetry()
        .unwrap();

    let tracer = global::tracer("app_or_crate_name");

    tracer.in_span("root-span", |cx| {
        thread::sleep(Duration::from_millis(5));

        tracer.in_span("GET /posts/:id", |cx| {
            thread::sleep(Duration::from_millis(10));

            let span = cx.span();
            span.set_attribute(Key::new("http.method").string("GET"));
            span.set_attribute(Key::new("http.route").string("/posts/:id"));
            span.set_attribute(Key::new("http.url").string("http://localhost:8080/posts/123"));
            span.set_attribute(Key::new("http.status_code").i64(200));
        });

        tracer.in_span("SELECT", |cx| {
            thread::sleep(Duration::from_millis(20));

            let span = cx.span();
            span.set_attribute(KeyValue::new("db.system", "mysql"));
            span.set_attribute(KeyValue::new(
                "db.statement",
                "SELECT * FROM table LIMIT 100",
            ));
        });

        let span = cx.span();
        println!(
            "https://app.uptrace.dev/traces/{}",
            span.span_context().trace_id().to_string()
        );
    });

    global::shutdown_tracer_provider();
}

Concurrency와 Parallelism이해하기|🔝|

https://spacebike.tistory.com/22

Multithreading for Beginners | freeCodeCamp.org|🔝|

rust -memory-container|🔝|

https://github.com/usagi/rust-memory-container-cs


  • small size ver.

rust-memory-container-cs-small-dark-back-high-contrast


Ownership Concept Diagram|🔝|

rust-ownvership

출처:

https://www.reddit.com/r/rust/comments/mgh9n9/ownership_concept_diagram/?utm_source=share&utm_medium=ios_app&utm_name=iossmf


Screenshot 2023-01-21 at 10 56 20 AM
220607자바(Java)vs러스트비교하면서 러스트오너쉽개념이해기본syntax연습하기part3_#java #rust #ownership


출처

Rust for Java Developers 3/3 - Understanding Ownership

Rust소유권 규칙Ownership Rules & Borrowing rules

Rust) shared reference ❤️ unique reference



Send & Mutex | Özgün Özerk|🔝|


Compact and efficient synchronization primitives for Rust. Also provides an API for creating custom synchronization primitives.|🔝|


C언어 러스트 이해하기

Async Engine in C | Tsoding Daily|🔝|

Rust Optimization최적화

모든 언어별 공통적인 최적화 개념

link



Temporal locality in memory mountain|🔝|


모든 프로그래머들이 알아야 할 컴퓨터의 시간 정리|🔝|

Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy             3,000   ns        3 us
Send 1K bytes over 1 Gbps network       10,000   ns       10 us
Read 4K randomly from SSD*             150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
Disk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from disk    20,000,000   ns   20,000 us   20 ms  80x memory, 20X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

Notes
-----
1 ns = 10^-9 seconds
1 us = 10^-6 seconds = 1,000 ns
1 ms = 10^-3 seconds = 1,000 us = 1,000,000 ns

Credit
------
By Jeff Dean:               http://research.google.com/people/jeff/
Originally by Peter Norvig: http://norvig.com/21-days.html#answers

Contributions
-------------
'Humanized' comparison:  https://gist.github.com/hellerbarde/2843375
Visual comparison chart: http://i.imgur.com/k0t1e.png

프로그래머가 알아야 할 지연 시간 숫자를 시각적으로 표현[🔝]

GN⁺: 모든 프로그래머가 알아야 할 필수 숫자들

  • https://samwho.dev/numbers/?fo
  • L1 캐시 참조: 1나노초
  • 분기 예측 실패: 3나노초
  • L2 캐시 참조: 4나노초
  • 뮤텍스 잠금/해제: 17나노초
  • 1 Gbps 네트워크를 통한 1KB 데이터 전송: 44나노초
  • 주 메모리 참조: 100나노초
  • Zippy를 이용한 1KB 데이터 압축: 2마이크로초
  • 메모리에서 1MB 순차 읽기: 3마이크로초
  • SSD에서 4K 무작위 읽기: 16마이크로초
  • SSD에서 1MB 순차 읽기: 49마이크로초
  • 동일 데이터센터 내 왕복 시간: 500마이크로초
  • 디스크에서 1MB 순차 읽기: 825마이크로초
  • 디스크 탐색: 2밀리초
  • 캘리포니아에서 네덜란드까지 패킷 전송 후 돌아오기: 150밀리초
Operationnsµsmsnote
L1 cache reference0.5 ns
Branch mispredict5 ns
L2 cache reference7 ns14x L1 cache
Mutex lock/unlock25 ns
Main memory reference100 ns20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy3,000 ns3 µs
Send 1K bytes over 1 Gbps network10,000 ns10 µs
Read 4K randomly from SSD*150,000 ns150 µs~1GB/sec SSD
Read 1 MB sequentially from memory250,000 ns250 µs
Round trip within same datacenter500,000 ns500 µs
Read 1 MB sequentially from SSD*1,000,000 ns1,000 µs1 ms~1GB/sec SSD, 4X memory
Disk seek10,000,000 ns10,000 µs10 ms20x datacenter roundtrip
Read 1 MB sequentially from disk20,000,000 ns20,000 µs20 ms80x memory, 20X SSD
Send packet CA -> Netherlands -> CA150,000,000 ns150,000 µs150 ms

지그 창시자가 설명해 주는 Operation Cost in CPU Cycles & Andrew Kelley Practical Data Oriented Design (DoD)[🔝]

Screenshot 2024-07-19 at 9 24 35 PM

  • Andrew Kelley Practical Data Oriented Design (DoD) | ChimiChanga(5min50sec)
    • https://youtu.be/IroPQ150F6c?si=tOxqzFtk5hkuWwYt

시대별로 단위가 생긴거 표로 잘 정리됨(Mertic_prefix_pico_kilo_nano..etc.[🔝]

Prefix Base 10 Decimal Adoption
[nb 1]
Name Symbol
quetta Q 1030 1000000000000000000000000000000 2022[3]
ronna R 1027 1000000000000000000000000000
yotta Y 1024 1000000000000000000000000 1991
zetta Z 1021 1000000000000000000000
exa E 1018 1000000000000000000 1975[4]
peta P 1015 1000000000000000
tera T 1012 1000000000000 1960
giga G 109 1000000000
mega M 106 1000000 1873
kilo k 103 1000 1795
hecto h 102 100
deca da 101 10
100 1
deci d 10−1 0.1 1795
centi c 10−2 0.01
milli m 10−3 0.001
micro μ 10−6 0.000001 1873
nano n 10−9 0.000000001 1960
pico p 10−12 0.000000000001
femto f 10−15 0.000000000000001 1964
atto a 10−18 0.000000000000000001
zepto z 10−21 0.000000000000000000001 1991
yocto y 10−24 0.000000000000000000000001
ronto r 10−27 0.000000000000000000000000001 2022[3]
quecto q 10−30 0.000000000000000000000000000001
Notes
  1. ^ Prefixes adopted before 1960 already existed before SI. The introduction of the CGS system was in 1873.

SI 접두어
v d e h
10n 접두어 기호 배수 십진수
1030 퀘타 (quetta) Q 1 000 000 000 000 000 000 000 000 000 000
1027 론나 (ronna) R 1 000 000 000 000 000 000 000 000 000
1024 요타 (yotta) Y 1 000 000 000 000 000 000 000 000
1021 제타 (zetta) Z 1 000 000 000 000 000 000 000
1018 엑사 (exa) E 1 000 000 000 000 000 000
1015 페타 (peta) P 1 000 000 000 000 000
1012 테라 (tera) T 1 000 000 000 000
109 기가 (giga) G 십억 1 000 000 000
106 메가 (mega) M 백만 1 000 000
103 킬로 (kilo) k 1 000
102 헥토 (hecto) h 100
101 데카 (deca) da 10
100 1
10−1 데시 (deci) d 십분의 일 0.1
10−2 센티 (centi) c 백분의 일 0.01
10−3 밀리 (milli) m 천분의 일 0.001
10−6 마이크로 (micro) µ 백만분의 일 0.000 001
10−9 나노 (nano) n 십억분의 일 0.000 000 001
10−12 피코 (pico) p 일조분의 일 0.000 000 000 001
10−15 펨토 (femto) f 천조분의 일 0.000 000 000 000 001
10−18 아토 (atto) a 백경분의 일 0.000 000 000 000 000 001
10−21 젭토 (zepto) z 십해분의 일 0.000 000 000 000 000 000 001
10−24 욕토 (yocto) y 일자분의 일 0.000 000 000 000 000 000 000 001
10−27 론토 (ronto) r 천자분의 일 0.000 000 000 000 000 000 000 000 001
10−30 퀙토 (quecto) q 백양분의 일 0.000 000 000 000 000 000 000 000 000 001

트위터 추천 알고리즘(scala로 작성됨)[🔝]


애니매이션으로 모든 물리학 공식과 같이 연관 되어 보기.. 진짜 대박 최고 !!❤[🔝]




그림으로 이해하는 Switch, if else, while, foreach, try, catch|🔝|

물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

Rust Compiler이해LLVM_컴파일과정

link


Rust컴파일 되는 과정|🔝|


Screenshot 2023-12-30 at 1 41 01 AM Screenshot 2023-12-30 at 1 40 22 AM


LLVM이 느려서 그 한계를 극복하기 위해 연구 중인 방안들|🔝|

LLVM IR and Rust(Rust와 LLVM의 관계)|🔝|

llvm -> clang C언어 C++
g++
clangd -> C언어의 LSP 지원 -> 타입이 나온다.type

c / c++ / zig  / rust 

  • Front-end: compiles source language to IR.
  • Middle-end: optimizes IR.
  • Back-end: compiles IR to machine code.

    Screenshot 2023-04-18 at 6 48 33 AM


    • src/main.rs
    use ndarray::{Array, Array1, ShapeBuilder};
    
    fn main() {
        let a = Array::from_shape_vec((3, 3), Array1::range(0., 9., 1.).to_vec());
        let b = Array::from_shape_vec((2, 2).strides((1, 2)), vec![1., 2., 3., 4.]).unwrap();
        println!("create array 01 bool : {:?}", a.is_ok());
        println!("create array : {:?}", b);
    }
    
    

    rust코드를 hir파일로 바꿔서 hir를 직접 확인해 보자|🔝|

    • cargo rustc -- -Zunpretty=hir
    cargo rustc -- -Zunpretty=hir
    
    info: syncing channel updates for 'nightly-x86_64-pc-windows-msvc'
    info: latest update on 2024-01-09, rust version 1.77.0-nightly (ca663b06c 2024-01-08)
    info: installing component 'rustfmt'
       Compiling autocfg v1.1.0
       Compiling rawpointer v0.2.1
       Compiling num-traits v0.2.17
       Compiling num-integer v0.1.45
       Compiling matrixmultiply v0.3.8
       Compiling num-complex v0.4.4
       Compiling ndarray v0.15.6
       Compiling testrust01 v0.1.0 (D:\young_linux\11111\testrust01)
    #[prelude_import]
    use std::prelude::rust_2021::*;
    #[macro_use]
    extern crate std;
    use ndarray::{};
    use ndarray::Array;
    use ndarray::Array1;
    use ndarray::ShapeBuilder;
    
    fn main() {
            let a =
                Array::from_shape_vec((3, 3), Array1::range(0., 9., 1.).to_vec());
            let b =
                Array::from_shape_vec((2, 2).strides((1, 2)),
                        <[_]>::into_vec(
                            #[rustc_box]
                            ::alloc::boxed::Box::new([1., 2., 3., 4.]))).unwrap();
            {
                    ::std::io::_print(format_arguments::new_v1(&["create array 01 bool : ",
                                        "\n"], &[format_argument::new_debug(&a.is_ok())]));
                };
            {
                    ::std::io::_print(format_arguments::new_v1(&["create array : ",
                                        "\n"], &[format_argument::new_debug(&b)]));
                };
        }
        Finished dev [unopt
    

    rust코드를 mir파일로 바꿔서 mir를 직접 확인해 보자|🔝|

    • cargo asm

      • https://github.com/gnzlbg/cargo-asm
    • cargo rustc -- -Zunpretty=mir

    cargo rustc -- -Zunpretty=mir
       Compiling testrust01 v0.1.0 (D:\young_linux\11111\testrust01)
    // WARNING: This output format is intended for human consumers only
    // and is subject to change without notice. Knock yourself out.
    fn main() -> () {
        let mut _0: ();
        let _1: std::result::Result<ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Di
    m<[usize; 2]>>, ndarray::ShapeError>;
        let mut _2: (usize, usize);
        let mut _3: std::vec::Vec<f64>;
        let mut _4: &ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 1]>>;
        let _5: ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 1]>>;
        let mut _7: std::result::Result<ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray
    ::Dim<[usize; 2]>>, ndarray::ShapeError>;
        let mut _8: ndarray::StrideShape<ndarray::Dim<[usize; 2]>>;
        let mut _9: (usize, usize);
        let mut _10: (usize, usize);
        let mut _11: std::vec::Vec<f64>;
        let mut _12: std::boxed::Box<[f64]>;
        let mut _13: usize;
        let mut _14: usize;
        let mut _15: *mut u8;
        let mut _16: std::boxed::Box<[f64; 4]>;
        let _17: ();
        let mut _18: std::fmt::Arguments<'_>;
        let mut _19: &[&str];
        let mut _20: &[core::fmt::rt::Argument<'_>];
        let _21: &[core::fmt::rt::Argument<'_>; 1];
        let _22: [core::fmt::rt::Argument<'_>; 1];
        let mut _23: core::fmt::rt::Argument<'_>;
        let _24: &bool;
        let _25: bool;
        let mut _26: &std::result::Result<ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarr
    ay::Dim<[usize; 2]>>, ndarray::ShapeError>;
        let _27: ();
        let mut _28: std::fmt::Arguments<'_>;
        let mut _29: &[&str];
        let mut _30: &[core::fmt::rt::Argument<'_>];
        let _31: &[core::fmt::rt::Argument<'_>; 1];
        let _32: [core::fmt::rt::Argument<'_>; 1];
        let mut _33: core::fmt::rt::Argument<'_>;
        let _34: &ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 2]>>;
        let mut _37: *const [f64; 4];
        scope 1 {
            debug a => _1;
            let _6: ndarray::ArrayBase<ndarray::OwnedRepr<f64>, ndarray::Dim<[usize; 2]>>;
            let mut _38: *const ();
            let mut _39: usize;
            let mut _40: usize;
            let mut _41: usize;
            let mut _42: usize;
            let mut _43: bool;
            scope 2 {
                debug b => _6;
                let mut _35: &[&str; 2];
                let mut _36: &[&str; 2];
            }
            scope 3 {
            }
        }
    
        bb0: {
            _2 = (const 3_usize, const 3_usize);
            _5 = ndarray::impl_constructors::<impl ArrayBase<OwnedRepr<f64>, Dim<[usize; 1]
    >>>::range(const 0f64, const 9f64, const 1f64) -> [return: bb1, unwind continue];
        }
    
        bb1: {
            _4 = &_5;
            _3 = ndarray::impl_1d::<impl ArrayBase<OwnedRepr<f64>, Dim<[usize; 1]>>>::to_ve
    c(move _4) -> [return: bb2, unwind: bb21];
        }
    
        bb2: {
            _1 = ndarray::impl_constructors::<impl ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]
    >>>::from_shape_vec::<(usize, usize)>(move _2, move _3) -> [return: bb3, unwind: bb21];
        }
    
        bb3: {
            drop(_5) -> [return: bb4, unwind: bb20];
        }
    
        bb4: {
            _9 = (const 2_usize, const 2_usize);
            _10 = (const 1_usize, const 2_usize);
            _8 = <(usize, usize) as ShapeBuilder>::strides(move _9, move _10) -> [return: b
    b5, unwind: bb20];
        }
    
        bb5: {
            _13 = SizeOf([f64; 4]);
            _14 = AlignOf([f64; 4]);
            _15 = alloc::alloc::exchange_malloc(move _13, move _14) -> [return: bb6, unwind
    : bb20];
        }
    
        bb6: {
            _16 = ShallowInitBox(move _15, [f64; 4]);
            _37 = (((_16.0: std::ptr::Unique<[f64; 4]>).0: std::ptr::NonNull<[f64; 4]>).0:
    *const [f64; 4]);
            _38 = _37 as *const () (PtrToPtr);
            _39 = _38 as usize (Transmute);
            _40 = AlignOf([f64; 4]);
            _41 = Sub(_40, const 1_usize);
            _42 = BitAnd(_39, _41);
            _43 = Eq(_42, const 0_usize);
            assert(_43, "misaligned pointer dereference: address must be a multiple of {} b
    ut is {}", _40, _39) -> [success: bb23, unwind unreachable];
        }
    
        bb7: {
            _7 = ndarray::impl_constructors::<impl ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]
    >>>::from_shape_vec::<StrideShape<Dim<[usize; 2]>>>(move _8, move _11) -> [return: bb8,
     unwind: bb20];
        }
    
        bb8: {
            _6 = Result::<ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>>, ShapeError>::unwrap(m
    ove _7) -> [return: bb9, unwind: bb20];
        }
    
        bb9: {
            _36 = const _;
            _19 = _36 as &[&str] (PointerCoercion(Unsize));
            _26 = &_1;
            _25 = Result::<ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>>, ShapeError>::is_ok(m
    ove _26) -> [return: bb10, unwind: bb19];
        }
    
        bb10: {
            _24 = &_25;
            _23 = core::fmt::rt::Argument::<'_>::new_debug::<bool>(_24) -> [return: bb11, u
    nwind: bb19];
        }
    
        bb11: {
            _22 = [move _23];
            _21 = &_22;
            _20 = _21 as &[core::fmt::rt::Argument<'_>] (PointerCoercion(Unsize));
            _18 = Arguments::<'_>::new_v1(move _19, move _20) -> [return: bb12, unwind: bb1
    9];
        }
    
        bb12: {
            _17 = _print(move _18) -> [return: bb13, unwind: bb19];
        }
    
        bb13: {
            _35 = const _;
            _29 = _35 as &[&str] (PointerCoercion(Unsize));
            _34 = &_6;
            _33 = core::fmt::rt::Argument::<'_>::new_debug::<ArrayBase<OwnedRepr<f64>, Dim<
    [usize; 2]>>>(_34) -> [return: bb14, unwind: bb19];
        }
    
        bb14: {
            _32 = [move _33];
            _31 = &_32;
            _30 = _31 as &[core::fmt::rt::Argument<'_>] (PointerCoercion(Unsize));
            _28 = Arguments::<'_>::new_v1(move _29, move _30) -> [return: bb15, unwind: bb1
    9];
        }
    
        bb15: {
            _27 = _print(move _28) -> [return: bb16, unwind: bb19];
        }
    
        bb16: {
            drop(_6) -> [return: bb17, unwind: bb20];
        }
    
        bb17: {
            drop(_1) -> [return: bb18, unwind continue];
        }
    
        bb18: {
            return;
        }
    
        bb19 (cleanup): {
            drop(_6) -> [return: bb20, unwind terminate(cleanup)];
        }
    
        bb20 (cleanup): {
            drop(_1) -> [return: bb22, unwind terminate(cleanup)];
        }
    
        bb21 (cleanup): {
            drop(_5) -> [return: bb22, unwind terminate(cleanup)];
        }
    
        bb22 (cleanup): {
            resume;
        }
    
        bb23: {
            (*_37) = [const 1f64, const 2f64, const 3f64, const 4f64];
            _12 = move _16 as std::boxed::Box<[f64]> (PointerCoercion(Unsize));
            _11 = slice::<impl [f64]>::into_vec::<std::alloc::Global>(move _12) -> [return:
     bb7, unwind: bb20];
        }
    }
    
    promoted[0] in main: &[&str; 2] = {
        let mut _0: &[&str; 2];
        let mut _1: [&str; 2];
    
        bb0: {
            _1 = [const "create array : ", const "\n"];
            _0 = &_1;
            return;
        }
    }
    
    promoted[1] in main: &[&str; 2] = {
        let mut _0: &[&str; 2];
        let mut _1: [&str; 2];
    
        bb0: {
            _1 = [const "create array 01 bool : ", const "\n"];
            _0 = &_1;
            return;
        }
    }
        Finished dev [unoptimized + debuginfo] target(s) in 0.67s
    

    rust코드를 llvm파일로 바꿔서 llvm을 직접 확인해 보자|🔝|

    • cargo rustc -- --emit llvm-ir && cat .\target\debug\deps\testrust01.ll
    
    $ cargo rustc -- --emit llvm-ir && cat .\target\debug\deps\testrust01.ll
    
    
    ...
    ...
    ...
    
    코드가 겁나게 많다. 
    
    ...
    !12775 = distinct !DISubprogram(name: "new<ndarray::ArrayBase<ndar
    ray::data_repr::OwnedRepr<f64>,ndarray::dimension::dim::Dim<array$
    <usize,2> > > >", linkageName: "_ZN4core3fmt2rt8Argument3new17h0fb
    bb2618fd00175E", scope: !3030, file: !3029, line: 83, type: !12776
    , scopeLine: 83, flags: DIFlagPrototyped, spFlags: DISPFlagLocalTo
    Unit | DISPFlagDefinition, unit: !330, templateParams: !3989, decl
    aration: !12779, retainedNodes: !12780)
    !12776 = !DISubroutineType(types: !12777)
    !12777 = !{!3030, !8337, !12778}
    !12778 = !DIDerivedType(tag: DW_TAG_pointer_type, name: "enum2$<co
    re::result::Result<tuple$<>,core::fmt::Error> > (*)(ref$<ndarray::
    ArrayBase<ndarray::data_repr::OwnedRepr<f64>,ndarray::dimension::d
    im::Dim<array$<usize,2> > > >,ref_mut$<core::fmt::Formatter>)", ba
    seType: !8553, size: 64, align: 64, dwarfAddressSpace: 0)
    !12779 = !DISubprogram(name: "new<ndarray::ArrayBase<ndarray::data
    _repr::OwnedRepr<f64>,ndarray::dimension::dim::Dim<array$<usize,2>
     > > >", linkageName: "_ZN4core3fmt2rt8Argument3new17h0fbbb2618fd0
    0175E", scope: !3030, file: !3029, line: 83, type: !12776, scopeLi
    ne: 83, flags: DIFlagPrototyped, spFlags: DISPFlagLocalToUnit, tem
    plateParams: !3989)
    !12780 = !{!12773, !12781}
    !12781 = !DILocalVariable(name: "f", arg: 2, scope: !12774, file:
    !3029, line: 83, type: !12778)
    !12782 = !DILocation(line: 83, scope: !12774, inlinedAt: !12783)
    !12783 = distinct !DILocation(line: 101, scope: !12766, inlinedAt:
     !12772)
    !12784 = !DILocation(line: 101, scope: !12766, inlinedAt: !12772)
    !12785 = !DILocation(line: 92, scope: !12774, inlinedAt: !12783)
    !12786 = !DILocation(line: 102, scope: !12766, inlinedAt: !12772)
    !12787 = !DILocation(line: 7, scope: !12733)
    !12788 = !DILocation(line: 3, scope: !12727)
    
    

    명령어 모아보기(hir, mir, miri파일 변환)|🔝|

    • cargo hir
      • https://gist.github.com/niklasad1/b838695ef436a0a16d5cd80cf462905f

    Expand macros

    $ cargo rustc --profile=check -- -Zunpretty=expanded


    $ cargo expand

    • https://github.com/dtolnay/cargo-expand

    Emit asm

    $ cargo rustc -- --emit asm && cat target/debug/deps/project_name-hash.s


    $ cargo rustc -- --emit asm=asssembly.s

    Emit llvm-ir

    $ cargo rustc -- --emit llvm-ir && cat target/debug/deps/project_name-hash.ll


    $ cargo rustc -- --emit llvm-ir=testrust.ll

    Emit HIR

    $ cargo rustc -- -Zunpretty=hir

    Emit MIR

    $ cargo rustc -- -Zunpretty=mir


    $ cargo rustc -- --emit mir=testrust.mir

    cargo rustc -- --emit dep-info=testrust.depinfo

    cargo rustc -- --emit dep-info=testrust.depinfo
    

    cargo rustc -- --emit help

    cargo rustc -- --emit help
       Compiling testrust01 v0.1.0 (D:\young_linux\11111\testrust01)
    error: unknown emission type: `help` - expected one of:
    
    `llvm-bc`,
    `asm`,
    `llvm-ir`,
     `mir`,
    `obj`,
    `metadata`,
    `link`,
    `dep-info`
    

    .pdb

    • Microsoft released the source code of their PDB formats, so other compiler developers like the LLVM team can implement the PDB format easier.
      • https://github.com/Microsoft/microsoft-pdb/
        • To actually dump the output of a file, just use this:
          • https://github.com/Microsoft/microsoft-pdb/blob/master/cvdump/cvdump.exe

    Rust Optimization

    link



    Rust Benchmarking|🔝|


    Rust Optimization|🔝|

    Rust eBook Rust High Performance:by Iban Eguia Moraza (Author)($유료)|🔝|

    Rust (Best pointer explanation)|🔝|

    • ThePrimeTime | best pointer explanation

    물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

    Vector를 array로 변환

    link


    Vector를 array로 변환

    // rust
    
    fn iter_to_array<Element, const N: usize>(mut iter: impl Iterator<Item = Element>) -> [Element; N] {
        // Here I use `()` to make array zero-sized -> no real use in runtime.
        // `map` creates new array, which we fill by values of iterator.
        let res: [_; N] = std::array::from_fn(|_| iter.next().unwrap());
        // Ensure that iterator finished
        // assert!(matches!(iter.next(), None));
        assert!(iter.next().is_none());
        res
    }
    
    fn main() {
        let my_vec = vec![1, 2, 3, 4, 5, 7];
        let my_array: [&i32; 6] = iter_to_array(my_vec.iter());
        println!("my array : {:?}", my_array);
    }
    • result
    my array : [1, 2, 3, 4, 5, 7]
    

    문제해결Rust코드로 해결

    정렬(Sort)



    물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

    검색(Search)



    물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

    문자열 패턴 매칭(SPM: String Pattern Matching)



    물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

    csv문서에서 내가 원하는정보 뽑아내기

    Q0001: csv에서 2020년도 1월 이후 입사한 직원을 뽑아내라.

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    A0001_Q0001로 이동

    • Q0001: csv에서 2020년도 1월 이후 입사한 직원을 뽑아내라.
    
    use chrono::NaiveDate;
    use std::error::Error;
    use std::fs::File;
    use std::io::{BufRead, BufReader};
    use std::str::FromStr; // Add chrono crate for date parsing
    
    fn main() -> Result<(), Box<dyn Error>> {
        let file = File::open("employees-with-date.csv")?;
        let reader = BufReader::new(file);
        let mut lines: Vec<String> = Vec::new();
    
        for line in reader.lines() {
            let line = line?;
            lines.push(line);
        }
    
        let threshold_date = NaiveDate::from_str("2020-01-01")?; // Set the threshold date (Jan 1, 2020)
    
        println!("Lines in the file with date after January 2020:");
    
        for (i, line) in lines.iter().enumerate() {
            // Split the line into columns (assume CSV format with commas)
            let columns: Vec<&str> = line.split(',').collect();
    
            if columns.len() >= 2 {
                // Parse the date from the second column (assuming it's in YYYY-MM-DD format)
                if let Ok(date) = NaiveDate::from_str(columns[3]) {
                    if date > threshold_date {
                        // If the date is after Jan 1, 2020, print the line
                        println!("{}: {}", i + 1, line);
    
                        // Optionally, debug the line
                        let mut my_vec: Vec<_> = vec![];
                        my_vec.push(line);
                        dbg!(my_vec);
                    }
                }
            }
        }
    
        Ok(())
    }

    계산(Calculation)



    물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|

    x^3계산

    Q0001: X에 3승을 구현해주세요.

    \[y = x^3 \]

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    A0001_Q0001로 이동

    use std::ops::Mul;
    
    fn xxx<T>(x: T) -> T
    where
        T: Mul<Output = T> + Copy,
    {
        x * x * x
    }
    
    fn main() {
        println!("(3)x^ 3 : {}", xxx(3));
    }
    (3)x^ 3 : 27  
    

    짝수만 계산&받은 데이터롤 모두 더하기(+)

    Q0001: 고객이 (1부터 10) 숫자를 다 더해 달라고 한다 .구현.

    Q0002: 고객이 (1부터 10) 짝수숫자를 다 더해 달라고 한다 (1개의 기능에 2가지가 바로 되게).구현.

    Q0003: 고객이 맘에 변해서 더 하는 기능 1개 짝수만 더하는 기능을 따로 구해해 달라고 한다. (1 ~ 10) 짝수숫자를 다 더해 달라고 한다 (1개의 기능에 2가지가 바로 되게).구현.

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    공백 답은 맨 아래

    A0001_Q0001로 이동

    • Q0001: 고객이 (1부터 10) 숫자를 다 더해 달라고 한다 .구현.
    use std::iter::Sum;
    
    fn intsum<T>(x: Vec<T>) -> T
    where
        T: Sum + Copy,
    {
        x.into_iter().sum()
    }
    
    fn main() {
        let my_vec: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        println!("Sum: {}", intsum(my_vec));
    }
    
    
    Sum: 55
    

    A0002_Q0002로 이동

    • Q0002: 고객이 (1부터 10) 짝수숫자를 다 더해 달라고 한다 (1개의 기능에 2가지가 바로 되게).구현.
    
    use std::iter::Sum;
    use std::ops::Rem;
    
    fn intsum_evensum<T>(x: Vec<T>) -> (T, T)
    where
        T: Sum + Copy + From<u8> + Rem<Output = T> + PartialEq,
    {
        // Compute the sum of all elements
        let all_sum = x.iter().cloned().sum();
    
        // Compute the sum of even elements
        let even_sum = x
            .iter()
            .cloned()
            .filter(|&n| n % T::from(2) == T::from(0))
            .sum();
    
        (all_sum, even_sum)
    }
    
    fn main() {
        let my_vec: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        let (all_sum, even_sum) = intsum_evensum(my_vec);
        println!("Total Sum: {}", all_sum);
        println!("Even Numbers Sum: {}", even_sum);
    }
    • Result
    Total Sum: 55
    Even Numbers Sum: 30
    

    A0003_Q0003로 이동

    • Q0003: 고객이 맘에 변해서 더 하는 기능 1개 짝수만 더하는 기능을 따로 구해해 달라고 한다. (1 ~ 10) 짝수숫자를 다 더해 달라고 한다 (1개의 기능에 2가지가 바로 되게).구현.
      • 소유권 문제를 피하기 위해 &로 구현
    use std::iter::Sum;
    use std::ops::Rem;
    
    fn all_sum<T>(x: &Vec<T>) -> T
    where
        T: Sum + Copy,
    {
        x.iter().cloned().sum()
    }
    
    fn even_sum<T>(x: &Vec<T>) -> T
    where
        T: Sum + Copy + From<u8> + Rem<Output = T> + PartialEq,
    {
        x.iter()
            .cloned()
            .filter(|n| *n % T::from(2) == T::from(0))
            .sum()
    }
    
    fn main() {
        let my_vec: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        let vec_all_sum = all_sum(&my_vec);
        let vec_even_sum = even_sum(&my_vec);
        println!("Total Sum: {:?}", vec_all_sum);
        println!("Even Numbers Sum: {:?}", vec_even_sum);
    }
    • Result
    Total Sum: 55
    Even Numbers Sum: 30
    

    BlackHatRust유료(영어원서)

    link


    Black Hat Rust로 공부하는 중 최고

    Rust OS

    link


    Rust OS 프로젝트

    Rust 로 OS만들고 있는 대표적인 프로젝트 2개 (Redux & Mastro)

    Redux

    Mastro ( Unix-like kernel written in Rust) blog.lenot.re


    Rust Embedded

    link