welcome소개
- 안녕하세요. 저는 러스트를 2021년 12월 easy rust를 만나 러스트를 시작하여 지금까지의 여정을 책으로 정리했습니다
-
비전공자로 최선을 다해 정리했으니 부족한 부분이 있거나 & 댓글에 알려주시거나 기여해 주시면 수정하도록 하겠습니다
-
C/C++/zig코드와 비교하면서 easy rust다음 단계의 책을 만들 예정입니다.
-
다음 장 부터는 편하게 말을 놓도록 하겠습니다.
- 부디 끝까지 살아남아 천상계에서 보도록 합시다. 화이팅!!
- 한국에서도 러스트OS 개발자가 나오는 그날을 향해
-
어려운 코드와 개발하기 힘들수록 러스트는 2가지만 기억하면 됩니다.(러스트를 하나로 관통하는 개념입니다.)
오너쉽OwnerShip
+ZeroCostAbstraction
-
Rust는 특유의 안전감에 빠져들게 되면 다른언어로 못 가실 겁니다.(안전하고 빠른 승차감 ㅋㅋ)
- 6개월정도는 오너쉽에 익숙해 지는 시간이 지옥같겠지만 익숙해 지시면 문법이 대부분 다 비슷비슷해서 갑자기 언어가 쉬워지는 마법이.. 그러니 끝까지 잘 살아남아보세용~
-
저같이 혼자 개발하는 분들을 위한 좋은글
-
Q&A & 질문하기 & 기여하기
물어보고 싶거나 하고 싶은말 써 주세요comment
Appendix
-
ComputerScience(CS, 컴퓨터싸이언스)
-
Assembly
Embebded|🔝|
- Omar Hiari | Simplified Embedded Rust: ESP Core Library Edition Kindle 에디션
- Simplified Embedded Rust: ESP Standard Library Edition
CS & OS|🔝|
-
CS안에 OS가 있다. CS먼저 보고 OS봐야한다.
-
CS
-
OS
Assembly|🔝|
- eBook)게임처럼 쉽고 재미있게 배우는 어셈블리 언어 튜토리얼 PDF 원일용 저 | 북스홀릭퍼블리싱 | 2021년 05월 20일
- Sandeep Ahluwalia Rust Under the Hood: A deep dive into Rust internals and generated assembly Kindle 에디션
justfile 러스트로 만든 makefile비슷한거
link
-
Assembly
- X86_64
- Arm
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
-
러스트 개발자 로드맵 Rust dev
-
Full-Stack
-
Roadmap(Backend)
-
Game개발 및 컴퓨터 CG & 애니매이션 & AI Network
Chapter 1(코딩의 원리)
-
코드는 바이트 덩어리 이다. 요즘 컴퓨터는 64bits로 처리하기 때문에 64bits를 한덩어리로 봐야한다
-
컴퓨터의 최적화 & 코드 만드는 원리의 기초는 CS(ComputerSystem) 와 OS(OperatingSystem)다. 기초 공부를 튼튼히 해야 고급에서도 무너지지 않는다.
- 어차피 사람이 만든거다. 어려운건 없다. 천천히 하면 된다.
- 안된다 생각하면 진짜 안된다. 나는 할수 있다. 100번씩 외치면서 기본부터 차근차근하자.
- 코딩은 하루아침에 안된다. 무슨일이든 10년은 해야 그때쯤 겨우 뭔가 나온다.(장인정신!! 우리는 예술가다.)
현대 언어는 AI와 결합한 코딩을 해야 살아 남는다. 무조건 AI를 활용하도록 하자
-
전체적인 그림에 집중하자. 어차피 AI세부적인것과 전체적으로 70~80프로 다 짜준다. 흐름에 집중하자.
-
AI(ChatGPT)
link
Rust 러스트 언어와 다른 언어의 차이점
러스트는 statement와 expression차이를 잘 알아야 한다.|🔝|
-
https://doc.rust-lang.org/reference/statements-and-expressions.html
-
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
-
struct & enum & trait를 쓰는 경우
-
Memory 기초지식
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 |
- File Size와 관계
- Text, data and bus: Code and Data Size Explained
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
- StackOverflowError 발생 종류
- 즉 스택 포인터가 스택의 경계를 넘어갈때 발생한다.
Heap Overflow[🔝]
Stack vs Heap의 장점[🔝]
- Stack
- 매우 빠른 액세스
- 변수를 명시적으로 할당 해제할 필요가 없다
- 공간은 CPU에 의해 효율적으로 관리되고 메모리는 단편화되지 않는다.
- 지역 변수만 해당된다
- 스택 크기 제한(OS에 따라 다르다)
- 변수의 크기를 조정할 수 없다
- Heap
- 변수는 전역적으로 액세스 할 수 있다.
- 메모리 크기 제한이 없다
- (상대적으로) 느린 액세스
- 효율적인 공간 사용을 보장하지 못하면 메모리 블록이 할당된 후 시간이 지남에 따라 메모리가 조각화 되어 해제될 수 있다.
- 메모리를 관리해야 한다 (변수를 할당하고 해제하는 책임이 있다)
- 변수는 자바 new를 사용
Rust String VS 다른 언어의 String의 차이점|🔝|
물어보고 싶거나 하고 싶은말 써 주세요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번뿐..
- 변수가 바뀐 다음부터는 읽기 전용상태(&)만 가능하다.
- 바꿀수 있는 기회는 딱 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
- Vector
- String
- HashMap
물어보고 싶거나 하고 싶은말 써 주세요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매트릭스 저장_다차원 구조로 저장
-
매트릭스 이해를 위한 선형대수학 기본 익히기Essence of linear algebra | 3Blue1Brown(모아보기)
-
다차원(tensor)을 이해하기 위해서는 선형대수학은 기본중에 기본 .
row와 columns 개념은 익히자
link
- C언어로 matrix저장
- [Rust언어로 matrix저장]
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
- 자료 출처 한글로 번역함 : https://github.com/pretzelhammer/rust-blog
Sizedness in Rust
- 22 July 2020 · #rust · #sizedness
link
Table of Contents
- Intro
- Sizedness
Sized
TraitSized
in Generics- Unsized Types
- Zero-Sized Types
- Conclusion
- Discuss
- Further Reading
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:
- 제가 사용하는 문구 표와 그 의미:
Phrase | Shorthand for |
---|---|
sizedness | property of being sized or unsized |
sized type | type 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 type | type that may or may not be sized |
unsized coercion | coercing a sized type into an unsized type |
ZST | zero-sized type, i.e. instances of the type are 0 bytes in size |
width | single 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 |
slice | double-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:
Type | T | &T |
---|---|---|
&str | T = &str | T = str |
Type | Sized |
---|---|
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 defaultSized
bound withT: ?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 forimpl 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:
Type | Pointer to Data | Data Length | Pointer to VTable | Total Width |
---|---|---|---|---|
&String | ✅ | ❌ | ❌ | 1 ✅ |
&str | ✅ | ✅ | ❌ | 2 ✅ |
&String as &dyn ToString | ✅ | ❌ | ✅ | 2 ✅ |
&str as &dyn ToString | ✅ | ✅ | ✅ | 3 ❌ |
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 defaultSized
bound withT: ?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 forimpl 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|🔝|
- Common Rust Lifetime Misconceptions
- Tour of Rust's Standard Library Traits
- Beginner's Guide to Concurrent Programming: Coding a Multithreaded Chat Server using Tokio
- Learning Rust in 2024
- Using Rust in Non-Rust Servers to Improve Performance
- RESTful API in Sync & Async Rust
- Learn Assembly with Entirely Too Many Brainfuck Compilers
C++오너쉽개념 기초 C++11에서 가져옴
OwnerShip밴다이어그램으로 소유권 이해
link
벤다이어그램 이해
오너쉽 관계를 잘이해하자(구현할 때 중요하다impl)
-
T
는 오너쉽을 가지고 있다.T
는 하위(&
,&mut
) 모두 커버되는 SuperSetT
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 다이어그램으로 이해하기
다른언어에서 러스트로 온 좋은 기능들..
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
발전 역사|🔝|
- Rust 1.82(241017)
- Rust 1.79(240613)
- Rust 1.61(220519)
- Rust 1.56(211021)
- Rust 1.51(210325)
- Rust 1.48(201119)
- Rust 1.36(190704)
- Rust 1.33(190228)
- Rust 1.31(181206)
Version 1.82.0 (2024-10-17)|🔝|
==========================
Language
- Don't make statement nonterminals match pattern nonterminals
- Patterns matching empty types can now be omitted in common cases
- Enforce supertrait outlives obligations when using trait impls
addr_of(_mut)!
macros and the newly stabilized&raw (const|mut)
are now safe to use with all static items- size_of_val_raw: for length 0 this is safe to call
- Reorder trait bound modifiers after
for<...>
binder in trait bounds - Stabilize opaque type precise capturing (RFC 3617)
- Stabilize
&raw const
and&raw mut
operators (RFC 2582) - Stabilize unsafe extern blocks (RFC 3484)
- Stabilize nested field access in
offset_of!
- Do not require
T
to be live when dropping[T; 0]
- Stabilize
const
operands in inline assembly - Stabilize floating-point arithmetic in
const fn
Version 1.79.0 (2024-06-13)
==========================
Language
- Stabilize inline
const {}
expressions. - Prevent opaque types being instantiated twice with different regions within the same function.
- Stabilize WebAssembly target features that are in phase 4 and 5.
- Add the
redundant_lifetimes
lint to detect lifetimes which are semantically redundant. - Stabilize the
unnameable_types
lint for public types that can't be named. - Enable debuginfo in macros, and stabilize
-C collapse-macro-debuginfo
and#[collapse_debuginfo]
. - Propagate temporary lifetime extension into
if
andmatch
expressions. - Restrict promotion of
const fn
calls.
Version 1.61.0 (2022-05-19)|🔝|
Language
- [
const fn
signatures can now include generic trait bounds][93827] - [
const fn
signatures can now useimpl 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 haveimpl Trait<'a, 'b>
where as before you could only haveimpl Trait<'a, 'b> where 'b: 'a
.
Version 1.51.0 (2021-03-25)|🔝|
============================
Language
-
[You can now parameterize items such as functions, traits, and
struct
s 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
, orchar
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 insideconst fn
now follows the same rules as inside afn
body.][75502] In particular,&foo()
will not be promoted to'static
lifetime any more insideconst fn
s.
Libraries
- [
mem::forget
is now aconst 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 todyn fmt::Debug + Send
, where this was previously not the case.
Libraries
- [
HashMap
's implementation has been replaced withhashbrown::HashMap
implementation.][58623] - [
TryFromSliceError
now implementsFrom<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 covers0..=255
and you would no longer be required to have a_ => unreachable!()
case. - [You can now have multiple patterns in
if let
andwhile let
expressions.][57532] You can do this with the same syntax as amatch
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
andwhile 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
, andPin
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 beimpl 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
orlet 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 theoverflowing_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 None
s.
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.
Symbol | Integer | bool | Floating Point | Overloading Trait |
---|---|---|---|---|
- | Negation* | Negation | std::ops::Neg | |
! | Bitwise NOT | Logical NOT | std::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.
Symbol | Integer | bool | Floating Point | Overloading Trait | Overloading Compound Assignment Trait |
---|---|---|---|---|---|
+ | Addition | Addition | std::ops::Add | std::ops::AddAssign | |
- | Subtraction | Subtraction | std::ops::Sub | std::ops::SubAssign | |
* | Multiplication | Multiplication | std::ops::Mul | std::ops::MulAssign | |
/ | Division*† | Division | std::ops::Div | std::ops::DivAssign | |
% | Remainder**† | Remainder | std::ops::Rem | std::ops::RemAssign | |
& | Bitwise AND | Logical AND | std::ops::BitAnd | std::ops::BitAndAssign | |
| | Bitwise OR | Logical OR | std::ops::BitOr | std::ops::BitOrAssign | |
^ | Bitwise XOR | Logical XOR | std::ops::BitXor | std::ops::BitXorAssign | |
<< | Left Shift | std::ops::Shl | std::ops::ShlAssign | ||
>> | Right Shift*** | std::ops::Shr | std::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.
Symbol | Meaning | Overloading method |
---|---|---|
== | Equal | std::cmp::PartialEq::eq |
!= | Not equal | std::cmp::PartialEq::ne |
> | Greater than | std::cmp::PartialOrd::gt |
< | Less than | std::cmp::PartialOrd::lt |
>= | Greater than or equal to | std::cmp::PartialOrd::ge |
<= | Less than or equal to | std::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 :
Expressionas
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 e | U | Cast performed by e as U |
---|---|---|
Integer or Float type | Integer or Float type | Numeric cast |
Enumeration | Integer type | Enum cast |
bool or char | Integer type | Primitive to integer cast |
u8 | char | u8 to char cast |
*T | *V where V: Sized * | Pointer to pointer cast |
*T where T: Sized | Integer type | Pointer to address cast |
Integer type | *V where V: Sized | Address to pointer cast |
&m₁ T | *m₂ T ** | Reference to pointer cast |
&m₁ [T; n] | *m₂ T ** | Array to pointer cast |
Function item | Function pointer | Function item to function pointer cast |
Function item | *V where V: Sized | Function item to pointer cast |
Function item | Integer | Function item to address cast |
Function pointer | *V where V: Sized | Function pointer to pointer cast |
Function pointer | Integer | Function pointer to address cast |
Closure *** | Function pointer | Closure 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 return0
- 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 tof32::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); }
- if necessary, rounding is according to
-
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 necessary, rounding is according to
* 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:
- Unit-only enums
- Field-less enums without explicit discriminants, or where only unit-variants have explicit discriminants
#![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 to0
,true
casts to1
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
andU
are both sized, the pointer is returned unchanged. -
If
T
andU
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 forstr
and any compound type whose unsized tail is a slice type, such asstruct Foo(i32, [u8])
or(u64, Foo)
. -
If
T
is unsized andU
is sized, the cast discards all metadata that completes the wide pointerT
and produces a thin pointerU
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 |
- ↑ 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. - ↑ 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). - ↑ The expression in the middle of the conditional operator (between
?
and:
) is parsed as if parenthesized: its precedence relative to?:
is ignored. - ↑ 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 |
+a |
!a |
a == b |
a[b] |
a(...) |
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
- C언어로 코드 구현하기
알고리즘의 4단계|🔝|
- 알고리즘은 4단계를 기억해야한다.
-
- 정렬(Sort)
-
- 검색(Search)
-
- 문자열 패턴 매칭(SPM: String Pattern Matching)
-
- 계산(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
- 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)
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 topic | Chapters for background reading |
---|---|
Architecture | 5, 11 |
Compilers | 6, 7, 8, 9, 10, 11, 12 |
Database systems | 11, 14, 15 |
Networking | 4, 13, 14 |
Operating systems | 11, 13, 14 |
Parallel and distributed systems | 11, 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 Edition 2024(rust_1_85)2025/02/20
- Rust Edition 2021(rust_1_56)2021/10/21
- Rust Edition 2018(rust_1_31)2018/12/06
- Rust Edition 2015(rust_1_00)2015y
Rust Relese 노트 미리 알아보기|🔝|
-
러스트 Weekly
-
Whatrustisit
Rust Version(2015년부터 러스트 역사 확인하기)|🔝|
- 🚀 Cutting-edge Features with 'Editions'
- Rust introduces significant changes or new features through ‘Editions’, which are released every 3 years.
- Rust Edition 2015(1.31.0 이전 버전은 Rust 2015 Edition)
- Rust Edition 2018(1.31.0 버전을 기점으로 Rust 2018 Edition으로 에디션이 변경)
- Rust Edition 2021(1.56.0 이후 버전은 Rust 2021 Edition)
- Rust2024 Edition (2025년 2월 나올 예정 Rust 1.85 on 2025-02-20)
rust_1_85_Rust Edition 2024
Rust 2024
Info | |
---|---|
RFC | #3501 |
Release version | 1.85.0 |
link
- Asynchrony & Iteration & Fallibility(async(await) & gen(for) & try(match))
- Rust 2024 주요 3가지 목표(중요한 3가지) & Rust 2024 목표(그외에 23가지 목표)
- Async Rust roadmap
(241130기준)현재는 nightly로 사용가능한듯 찾아보자
-
https://doc.rust-lang.org/nightly/edition-guide/rust-2024/index.html
-
https://blog.rust-lang.org/2024/11/27/Rust-2024-public-testing.html
Rust2024 Edition (2025년 beta버젼에 출시 예정250109 beta version에서 사용가능)
- 241127뉴스_rust2024 beta version에 250109 적용예정.
- Stable버젼은 Rust 1.85 on 2025-02-20
- Rust 2024 will enter the beta channel on 2025-01-09, and will be released to stable Rust with Rust 1.85 on 2025-02-20.
- Stable버젼은 Rust 1.85 on 2025-02-20
- (231215) https://blog.rust-lang.org/2023/12/15/2024-Edition-CFP.html
- (240812) (러스트 Blog)Rust Project goals for 2024
- Rust 2024 Edition
- Async
- Rust for Linux
Rust 2024 주요 3가지 목표(중요한 3가지)
1 | Bring the Async Rust experience closer to parity with sync Rust |
2 | Resolve the biggest blockers to Linux building on stable Rust |
3 | Rust 2024 Edition |
Rust 2024 목표(그외에 23가지 목표)
- Goal 러스트 에디션 2024 핵심 목표외 23가지 목표
1 | "Stabilizable" prototype for expanded const generics |
2 | Administrator-provided reasons for yanked crates |
3 | Assemble project goal slate |
4 | Associated type position impl trait |
5 | Begin resolving cargo-semver-checks blockers for merging into cargo |
6 | Const traits |
7 | Ergonomic ref-counting |
8 | Explore sandboxed build scripts |
9 | Expose experimental LLVM features for automatic differentiation and GPU offloading |
10 | Extend pubgrub to match cargo's dependency resolution |
11 | Implement "merged doctests" to save doctest time |
12 | Make Rustdoc Search easier to learn |
13 | Next-generation trait solver |
14 | Optimizing Clippy & linting |
15 | Patterns of empty types |
16 | Scalable Polonius support on nightly |
17 | Stabilize cargo-script |
18 | Stabilize doc_cfg |
19 | Stabilize parallel front end |
20 | Survey tools suitability for Std safety verification |
21 | Testing infra + contributors for a-mir-formality |
22 | Use annotate-snippets for rustc diagnostic output |
23 | User-wide build cache |
Asynchrony & Iteration & Fallibility(async(await) & gen(for) & try(match))|🔝|
- Patterns & Abstractions( March 14, 2023
- | ASYNCHRONY | ITERATION | FALLIBILITY |
---|---|---|---|
CONTEXT | async { } | gen { } | try { } |
EFFECT | yield | throw | |
FORWARD | .await | yield from | ? |
COMPLETE | spawn/block_on | for | match |
Async Rust roadmap|🔝|
Year | Language |
---|---|
2019 | Async fns |
2019-2022 | Ecosystem development |
2023 | Async fn in traits |
2024 | Async closures, generators.... |
-
Standard way to write async Rust that...비동기 Rust를 작성하는 표준 방법은...
- lets you gracefully handle cancellation and streams
- 취소 및 스트림을 우아하게 처리할 수 있습니다
- supports a rich, interopable ecosystem of middleware, logging,etc
- 미들웨어, 로깅 등의 풍부하고 상호 운용 가능한 에코시스템을 지원합니다
- works everywhere, from embedded to servers
- 내장된 서버에서 서버까지 모든 곳에서 작동합니다
- is easy to learn, well documented, and free of footguns
- 배우기 쉽고, 잘 문서화되어 있으며, 발총이 없습니다
- C++은 코드를 잘못 만들면 내 발에 총을 쏜다는걸 이야기하는듯 그래서 코드가 터져버리는 ㅋㅋㅋ
- 배우기 쉽고, 잘 문서화되어 있으며, 발총이 없습니다
- lets you gracefully handle cancellation and streams
-
Stabilizing async fn in traits in 2023(async fn 안정화했다는 러스트 블로그 글)
- May 3, 2023 · Niko Matsakis and Tyler Mandry on behalf of The Rust Async Working Group
- https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html
- 관련 Reddit글
-
Rust Async GAT관련
- https://rust-lang.github.io/rfcs/3185-static-async-fn-in-trait.html
- Try using GAT to improve Future's failed: How to declare the life cycle?
- https://rust-lang.github.io/rfcs/3185-static-async-fn-in-trait.html
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 byuse<..>
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 theelse
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.
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)] }
Details are documented at RFC 3606
Match ergonomics
This is a placeholder, docs coming soon!
Summary
Details
Migration
Unsafe extern
blocks
Summary
extern
blocks must now be marked with theunsafe
keyword.
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.
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
- The following attributes must now be marked as
unsafe
:
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.
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 nowdeny
by default. This checks for taking a shared or mutable reference to astatic 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
- Never type (
!
) to any type ("never-to-any") coercions fall back to never type (!
) rather than to unit type (()
). - The
never_type_fallback_flowing_into_unsafe
lint is nowdeny
by default.
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 supportsconst
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
- The
missing_fragment_specifier
lint is now a hard error.
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
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
gen
is a reserved keyword.
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
andIntoFuture
traits are now part of the prelude. - This might make calls to trait methods ambiguous which could make some code fail to compile.
RustcEncodable
andRustcDecodable
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:
- Added:
- Removed:
RustcEncodable
RustcDecodable
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 callIntoIterator::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
- The following functions are now marked
unsafe
:
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"
impliesresolver = "3"
inCargo.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
; usedefault-features
instead. - Removed
crate_type
; usecrate-type
instead. - Removed
proc_macro
; useproc-macro
instead. - Removed
dev_dependencies
; usedev-dependencies
instead. - Removed
build_dependencies
; usebuild-dependencies
instead.
- Removed
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 specifiesdefault-features = true
(or does not specifydefault-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.
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); //! ``` }
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 ofLocation
. 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
- Stabilize
&mut
,*mut
,&Cell
, and*const Cell
in const. - Allow creating references to statics in
const
initializers. - Implement raw lifetimes and labels (
'r#ident
). - Define behavior when atomic and non-atomic reads race.
- Non-exhaustive structs may now be empty.
- Disallow implicit coercions from places of type
!
const extern
functions can now be defined for other calling conventions.- Stabilize
expr_2021
macro fragment specifier in all editions. - The
non_local_definitions
lint now fires on less code and warns by default.
Compiler
- Deprecate unsound
-Csoft-float
flag. - Add many new tier 3 targets:
Refer to Rust's [platform support page][platform-support-doc] for more information on Rust's tiered platform support.
Libraries
- Implement
PartialEq
forExitCode
. - Document that
catch_unwind
can deal with foreign exceptions without UB, although the exact behavior is unspecified. - Implement
Default
forHashMap
/HashSet
iterators that don't already have it. - Bump Unicode to version 16.0.0.
- Change documentation of
ptr::add
/sub
to not claim equivalence withoffset
..
Stabilized APIs
BufRead::skip_until
ControlFlow::break_value
ControlFlow::continue_value
ControlFlow::map_break
ControlFlow::map_continue
DebugList::finish_non_exhaustive
DebugMap::finish_non_exhaustive
DebugSet::finish_non_exhaustive
DebugTuple::finish_non_exhaustive
ErrorKind::ArgumentListTooLong
ErrorKind::Deadlock
ErrorKind::DirectoryNotEmpty
ErrorKind::ExecutableFileBusy
ErrorKind::FileTooLarge
ErrorKind::HostUnreachable
ErrorKind::IsADirectory
ErrorKind::NetworkDown
ErrorKind::NetworkUnreachable
ErrorKind::NotADirectory
ErrorKind::NotSeekable
ErrorKind::ReadOnlyFilesystem
ErrorKind::ResourceBusy
ErrorKind::StaleNetworkFileHandle
ErrorKind::StorageFull
ErrorKind::TooManyLinks
Option::get_or_insert_default
Waker::data
Waker::new
Waker::vtable
char::MIN
hash_map::Entry::insert_entry
hash_map::VacantEntry::insert_entry
These APIs are now stable in const contexts:
Cell::into_inner
Duration::as_secs_f32
Duration::as_secs_f64
Duration::div_duration_f32
Duration::div_duration_f64
MaybeUninit::as_mut_ptr
NonNull::as_mut
NonNull::copy_from
NonNull::copy_from_nonoverlapping
NonNull::copy_to
NonNull::copy_to_nonoverlapping
NonNull::slice_from_raw_parts
NonNull::write
NonNull::write_bytes
NonNull::write_unaligned
OnceCell::into_inner
Option::as_mut
Option::expect
Option::replace
Option::take
Option::unwrap
Option::unwrap_unchecked
Option::<&_>::copied
Option::<&mut _>::copied
Option::<Option<_>>::flatten
Option::<Result<_, _>>::transpose
RefCell::into_inner
Result::as_mut
Result::<&_, _>::copied
Result::<&mut _, _>::copied
Result::<Option<_>, _>::transpose
UnsafeCell::get_mut
UnsafeCell::into_inner
array::from_mut
char::encode_utf8
{float}::classify
{float}::is_finite
{float}::is_infinite
{float}::is_nan
{float}::is_normal
{float}::is_sign_negative
{float}::is_sign_positive
{float}::is_subnormal
{float}::from_bits
{float}::from_be_bytes
{float}::from_le_bytes
{float}::from_ne_bytes
{float}::to_bits
{float}::to_be_bytes
{float}::to_le_bytes
{float}::to_ne_bytes
mem::replace
ptr::replace
ptr::slice_from_raw_parts_mut
ptr::write
ptr::write_unaligned
<*const _>::copy_to
<*const _>::copy_to_nonoverlapping
<*mut _>::copy_from
<*mut _>::copy_from_nonoverlapping
<*mut _>::copy_to
<*mut _>::copy_to_nonoverlapping
<*mut _>::write
<*mut _>::write_bytes
<*mut _>::write_unaligned
slice::from_mut
slice::from_raw_parts_mut
<[_]>::first_mut
<[_]>::last_mut
<[_]>::first_chunk_mut
<[_]>::last_chunk_mut
<[_]>::split_at_mut
<[_]>::split_at_mut_checked
<[_]>::split_at_mut_unchecked
<[_]>::split_first_mut
<[_]>::split_last_mut
<[_]>::split_first_chunk_mut
<[_]>::split_last_chunk_mut
str::as_bytes_mut
str::as_mut_ptr
str::from_utf8_unchecked_mut
Cargo
- Introduced a new
CARGO_MANIFEST_PATH
environment variable, similar toCARGO_MANIFEST_DIR
but pointing directly to the manifest file. - Added
package.autolib
to the manifest, allowing[lib]
auto-discovery to be disabled. - Declare support level for each crate in Cargo's Charter / crate docs.
- Declare new Intentional Artifacts as 'small' changes.
Rustdoc
- The sidebar / hamburger menu table of contents now includes the
# headers
from the main item's doc comment. This is similar to a third-party feature provided by the rustdoc-search-enhancements browser extension.
Compatibility Notes
-
Warn against function pointers using unsupported ABI strings.
-
Check well-formedness of the source type's signature in fn pointer casts. This partly closes a soundness hole that comes when casting a function item to function pointer
-
Use equality instead of subtyping when resolving type dependent paths.
-
Linking on macOS now correctly includes Rust's default deployment target. Due to a linker bug, you might have to pass
MACOSX_DEPLOYMENT_TARGET
or fix your#[link]
attributes to point to the correct frameworks. See https://github.com/rust-lang/rust/pull/129369. -
The future incompatibility lint
deprecated_cfg_attr_crate_type_name
has been made into a hard error. It was used to deny usage of#![crate_type]
and#![crate_name]
attributes in#![cfg_attr]
, which required a hack in the compiler to be able to change the used crate type and crate name after cfg expansion. Users can use--crate-type
instead of#![cfg_attr(..., crate_type = "...")]
and--crate-name
instead of#![cfg_attr(..., crate_name = "...")]
when runningrustc
/cargo rustc
on the command line. Use of those two attributes outside of#![cfg_attr]
continue to be fully supported. -
Until now, paths into the sysroot were always prefixed with
/rustc/$hash
in diagnostics, codegen, backtrace, e.g.thread 'main' panicked at 'hello world', map-panic.rs:2:50 stack backtrace: 0: std::panicking::begin_panic at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/std/src/panicking.rs:616:12 1: map_panic::main::{{closure}} at ./map-panic.rs:2:50 2: core::option::Option<T>::map at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/option.rs:929:29 3: map_panic::main at ./map-panic.rs:2:30 4: core::ops::function::FnOnce::call_once at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/ops/function.rs:248:5 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
We want to change this behaviour such that, when
rust-src
source files can be discovered, the virtual path is discarded and therefore the local path will be embedded, unless there is a--remap-path-prefix
that causes this local path to be remapped in the usual way.#129687 implements this behaviour, when
rust-src
is present at compile time,rustc
replaces/rustc/$hash
with a real path into the localrust-src
component with best effort. To sanitize this, users must explicitly supply--remap-path-prefix=<path to rust-src>=foo
or not have therust-src
component installed. -
The allow-by-default
missing_docs
lint used to disable itself when invoked throughrustc --test
/cargo test
, resulting in#[expect(missing_docs)]
emitting false positives due to the expectation being wrongly unfulfilled. This behavior has now been removed, which allows#[expect(missing_docs)]
to be fulfilled in all scenarios, but will also report newmissing_docs
diagnostics for publicly reachable#[cfg(test)]
items, integration test crate-level documentation, and publicly reachable items in integration tests. -
The
armv8r-none-eabihf
target now uses the Armv8-R required set of floating-point features. -
The sysroot no longer contains the
std
dynamic library in its top-levellib/
dir.
rust_1_56_Rust Edition 2021
Rust 2021
Info | |
---|---|
RFC | #3085 |
Release version | 1.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
andFromIterator
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
)
- 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.
- Is this an inherent method or a trait method?
- Inherent methods that take
self
will take priority overTryInto::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 (whileTryInto::try_into
does not, as it takesself
)
- Inherent methods that take
- 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
typedyn Trait
)- If so, trait imports don't affect resolution, and no migration lint needs to occur
Default Cargo feature resolver
Summary
edition = "2021"
impliesresolver = "2"
inCargo.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 toIntoIterator::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 onlya.x
instead ofa
.- This can cause things to be dropped at different times or affect whether closures implement traits like
Send
orClone
.- If possible changes are detected,
cargo fix
will insert statements likelet _ = &a
to force a closure to capture the entire variable.
- If possible changes are detected,
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:
Clone
: if all captured values areClone
.- Auto traits like
Send
,Sync
, andUnwindSafe
: if all captured values implement the given trait.
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 usesformat_args!(..)
, just likeprintln!()
.panic!("{")
is no longer accepted, without escaping the{
as{{
.panic!(x)
is no longer accepted ifx
is not a string literal.- Use
std::panic::panic_any(x)
to panic with a non-string payload. - Or use
panic!("{}", x)
to usex
'sDisplay
implementation.
- Use
- 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, whileasync
is not a keyword in edition 2015, this prefix would've allowed us to acceptk#async
in edition 2015 without having to wait for edition 2018 to reserveasync
as a keyword. -
f""
as a short-hand for a format string. For example,f"hello {name}"
as a short-hand for the equivalentformat!()
invocation. -
s""
forString
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
andellipsis_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
inmacro_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"
orcr"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
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
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 withcrate
,self
, orsuper
.
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
andfoo/
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, becausestd
is automatically imported unless the crate is marked with#![no_std]
.core
: Usually this is not necessary, becausecore
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 torustc
.alloc
: Items in thealloc
crate are usually accessed via re-exports in thestd
crate. If you are working with ano_std
crate that supports allocation, then you may need to explicitly importalloc
.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
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
Summary
- Trait function parameters may use any irrefutable pattern when the function has a body.
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
Summary
dyn
is a strict keyword, in 2015 it is a weak keyword.async
andawait
are strict keywords.try
is a reserved keyword.
Motivation
dyn Trait
for trait objects
The dyn Trait
feature is the new syntax for using trait objects. In short:
Box<Trait>
becomesBox<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
- The
tyvar_behind_raw_pointer
lint is now a hard error.
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 thepath
field is not set. cargo install
for the current directory is no longer allowed, you must specifycargo 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
-
기초 상식
-
러스트에 바로 쓸 기초 개념
- C언어로 러스트 이해하기
외부 자료
-
Concurrency & Parallelism
- https://github.com/LukeMathWalker/zero-to-production
- Concurrency
- https://rust-lang.github.io/async-book/
- https://github.com/tokio-rs/tokio
- Channels(MPSC)
- MPSC(multi-producer, single-consumer channel.)
- Channels(MPSC)
- https://github.com/hyperium/hyper
- Parallelism
- https://github.com/rayon-rs/rayon
- Channels(MPMC)
- MPMC(A blazingly fast multi-producer, multi-consumer channel.)
- MPMC초장기 모델
-
Async Book(Rust)
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
-
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.
- 비동기적으로 자신을 호출하려면 반복을 고정해야 합니다.
- A stream that relies upon another stream will typically pin its access to the previous stream.
- Pinning피닝을 사용하면 Rust에게 변수를 풀 때까지 같은 위치에 있어야 한다는 것을 알 수 있습니다.
출처 : 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.
Ownership Concept Diagram|🔝|
출처:
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|🔝|
- 27 May 2024
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|🔝|
-
https://stackoverflow.com/questions/56720935/temporal-locality-in-memory-mountain
-
시간 지역성을 계곡으로 표시(ridges of temporal locality, 시간적 국소성의 능선)
-
공간 지역성을 기울기로 표시(slopes of spatial locality, 공간적 국소성의 경사면)
모든 프로그래머들이 알아야 할 컴퓨터의 시간 정리|🔝|
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
프로그래머가 알아야 할 지연 시간 숫자를 시각적으로 표현[🔝]
- 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밀리초
Operation | ns | µs | ms | note |
---|---|---|---|---|
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 µs | ||
Send 1K bytes over 1 Gbps network | 10,000 ns | 10 µs | ||
Read 4K randomly from SSD* | 150,000 ns | 150 µs | ~1GB/sec SSD | |
Read 1 MB sequentially from memory | 250,000 ns | 250 µs | ||
Round trip within same datacenter | 500,000 ns | 500 µs | ||
Read 1 MB sequentially from SSD* | 1,000,000 ns | 1,000 µs | 1 ms | ~1GB/sec SSD, 4X memory |
Disk seek | 10,000,000 ns | 10,000 µs | 10 ms | 20x datacenter roundtrip |
Read 1 MB sequentially from disk | 20,000,000 ns | 20,000 µs | 20 ms | 80x memory, 20X SSD |
Send packet CA -> Netherlands -> CA | 150,000,000 ns | 150,000 µs | 150 ms |
지그 창시자가 설명해 주는 Operation Cost in CPU Cycles & Andrew Kelley Practical Data Oriented Design (DoD)[🔝]
- 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 | |
|
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로 작성됨)[🔝]
애니매이션으로 모든 물리학 공식과 같이 연관 되어 보기.. 진짜 대박 최고 !!❤[🔝]
-
Rust without crates.io
-
Memory Issues
-
switch문과 if문의 성능 비교 (ISA관점에서)
그림으로 이해하는 Switch, if else, while, foreach, try, catch|🔝|
물어보고 싶거나 하고 싶은말 써 주세요comment|🔝|
Rust Compiler이해LLVM_컴파일과정
link
Rust컴파일 되는 과정|🔝|
LLVM이 느려서 그 한계를 극복하기 위해 연구 중인 방안들|🔝|
-
gccrs
는 GCC 프로젝트의 일환으로 개발 중인 대체 Rust 컴파일러입니다.- 이 프로젝트는 GNU 컴파일러 컬렉션 내에서 Rust를 지원하는 것을 목표로 하며,
rustc
와 동일한 동작을 목표로 합니다. - 주요 목표는 특히 LLVM이 지원하지 않는 플랫폼에서 Rust를 컴파일할 수 있는 대안을 제공하는 것입니다.
LLVM IR and Rust(Rust와 LLVM의 관계)|🔝|
llvm -> clang C언어 C++
g++
clangd -> C언어의 LSP 지원 -> 타입이 나온다.type
c / c++ / zig / rust
- 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
- To actually dump the output of a file, just use this:
- https://github.com/Microsoft/microsoft-pdb/
Rust Optimization
link
- Rust Benchmarking(criterion)
- Rust Optimization
- Rust (Best pointer explanation)
- Rust eBook Rust High Performance:by Iban Eguia Moraza (Author)
Rust Benchmarking|🔝|
Rust Optimization|🔝|
-
Rust Optimization
-
날아갈까봐 Fork함
Rust eBook Rust High Performance:by Iban Eguia Moraza (Author)($유료)|🔝|
- Rust High Performance: Learn to skyrocket the performance of your Rust applications 1st Edition, Kindle Edition 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
- OS들어가기 전에 컴파일 만들기 책 보고 들어가야 좀 편하다.유명한 컴파일러 만들기 책