두번째 Rust 유튜브로 공부하기
https://play.rust-lang.org/
https://replit.com/language/rust
https://www.youtube.com/watch?v=W9DO6m8JSSs&list=PLfllocyHVgsSJf1zO6k6o3SX2mbZjAqYE&ab_channel=mithradates
http://doc.rust-lang.org/std
Rust → C, C++ 같이 빠르면서 안전한, 하지만 대신 많이 배워야 쓸 수 있는 언어.
까다로울 수록 Compiler가 뭐하는지 알 수 있고 해서 좀 편하다.
끝!
// 기본 주석
/* / C, C++과 같이 영역 주석
/// Document 파일을 만들때 사용되는 주석
let x = 10;
let x: i16 = 10;
let x/: i16*/ = 10;
위와 같이도 가능함
let x/: i16/ = 10;
처럼 언더바를 상용하면 일단 Compiler에게 무시하라고 시킬 수 있다.
i8, i16, i32, i64, i128, isize
u8, u16, u32, u64, u128, usize
같은 거를 사용할 수 있다.
아무 설정값 안주면 i32로 될 수도 있음 (정확히는 안써있)
서로 다른 타입이면 연산이 안됨
하지만, let으로 아무것도 안정해주면 좀 알아서 맞춰서 연산하게 되는 것 같음
char, mut
mut → mutable, 바꿀수있는 변수
char는 4 byte 크기를 사용하고 있음, 그래서 32-bit를 쓰는데 엄청 많이 표현함
ASCII는 32-bit까지는 필요없어서 casting해서 적게 쓸 수 있음
변수 a as u16 이런게 가능함. 그래서,
let a: u8
let b: u16
c = a + b as u8
로 할 수 있음
Way casting
ASCII (255개) → 8-bit 만으로 모두 표현이 가능함
let my_num = 'a' as u8; (가능)
let my_num = '№' as u8; (불가능)
아 진짜, 출력하는게 엄청 엄청 편해졌다... 와...
Compiler가 엄청 똑똑하게 동작하는 것 같네
char과 string의 차이
char은 4B 이지만, string은 몇 Byte일지 모름
rust에서는 len이라는 것이 Byte를 말하는 것이다.
그래서, "a".len() 이런식으로 사용이 가능함!! 미친;
그런데, 해보니까 길이가 나오는데?
아, 그러니까
"abcde".len() = 5
"abcd오".len() = 7
이렇게 나오는걸 봐서, 영어글자는 1 Byte, 한글은 3 Byte로 표현을 하네
.chars().count() 이런 것도 있음
"Hello there".chars*().count() → ('H', 'e', 'l', ...).count
이걸로 글자수를 체크할 수 있고
9u8 이런식으로 쉽게 쓸 수 있음, 9_u8, 9__u8 이런것도 가능
1___000__000u64 이런것도 가능
float는 f32, f64, 이런식으로 사용가능
거의다 f64를 쓰는 추세로 보임
let my_num = 9.6; // f64
let other_num = 9;
float에 as i32처럼 바꾸면 소수점 다 버림
rust의 재미있는 부분은 매크로를 많이 쓰는 것임
println (일반)
println! (매크로 함수)
매크로 → 약간 Superfunction
좀 복잡한 코드가 있으면, 좀 깔끔하게 편하게 쓸 수 있게 해줌
매크로 어떻게 했는지 볼 수 있음
TOOLS → expand macros
println! 안에 {} 여러개 둘 때 차례대로 들어가게 됨
fn give_age() -> i32 {
42
}
이런식을 함수를 정의하면 됨
return 값을 i32 이런식으로 씀
return 써도 되고 안써도 되고
println!("{a}") 이런식으로도 쓸 수 있음!!
println!("Name: {Name}, City: {City}, Age: {Age}", Name = a, City = b, Age = c);
println!("Name: {1}, City: {0}, Age: {2}", b, a, c);
근데 애초에 println!("Name: {a}, City: {b}, Age: {c}"); 가 엄청 편해보이네
let x = 9;
semicolon ;
() --> empty tuplepl
return으로 semicolon을 사용하면 void가 나옴. void는 empty_tuple () 형태
empty tuple은 println!이 불가능함
println!의 {}는 std::fmt::Display를 수행하는 것인데, empty tuple은 이를 지원하지 않음
지원하지 않는 것을 어떻게 출력하느냐..
Display {}
Debug print {:?}
위의 Debug print로 할 수가 있다. (대부분의 경우)
main은 return type을 마음대로 바꿀수가 없음
fn give_number(one: i32, two: i32) -> i32
이런 식으로 수행할 수 있음
내부에서 복잡한 수행을 한 다음에 변수 하나를 return 하고 싶으면 다음과 같이 수행가능
fn mul(a, b) -> i32 {
let result = {
let c = 12;
a b
};
result
}
mutability : 바꿀 수 있는지
immutable by defuault -> 바꿀 수 없다
mut으로 바꿀 수 있게 변경 가능
ex)
let a = 10; 이면
a = 9 이런거 들어오면 에러가 발생함
근데 let mut a = 10; 이면 수정 가능
그런데 가능하면 안바꾸는게 좀 안전하다
Shadowing : 같은 이름을 다시 쓰는 것
let num = 1;
let num = 2;
이런 식으로 덮어씌울 수 있음
컴파일러가 봤을때에는 문제가 없음
let x = double(x); 이런식으로도 수행가능
let a = 1;
{
let a = 2;
}
println!("{a}");
를 해보면, 1 값이 됨
Stack : 제일 빠르고, 1 - 2 - 3 이면 3 - 2 - 1로 되는
접근할 곳이 계속 바로 옆에 있어서 매우매우 빠름
Heap : 컨테이너 처럼 1 - 2 - 3 이면 1 - 2 - 3으로 나오는
근데 보통 list 처럼 수행해서 특정 위치의 값을 읽어오는 식으로 하고 있음
마치 책과 같음. 챕터1: 12pg, 챕터2: 25pg, 챕터3: 81pg ..
&&&&&a 처럼 reference 여러개 써서 할 수 있음
\n으로 새로운 line으로 가는거고 이건 c, c++하고 동일
\n 이런시으로 계속 쓰는 거는 좀 더러우니까
print!(r#"c:\thisdrive\new_drive"#);
이런식으로 깔끔하게 raw text만 출력할 수 있음
더나아가서,
println!("Let me
Tell you
This thing")
이런식으로도 다음 줄로 넘어갈 수 있음
println!("{:?}", )
let num = 9;
println!("{:p}", num) // pointer 출력
{:X} // Hexadecimal 출력
{:b} // binary로 출력
자세한건 Rust의 Module::std::fmt에서 확인할 수 있음
< 왼쪽, > 오른쪽, ^ 가운데
{:-^30} // 가운데 30개
{: <15} // 왼쪽에 15개
외울 필요는 없고 Module::std::fmt에서 할 수 있게
rust는 여러가지 string 타입을 제공, 총 8가지정도
거의 항상 String, &str를 사용
String: growable string, 더 많은 기능을 사용할 수 있음, owned type이 있음
owned type은 자기 타입을 가지고 있고, 그 데이터가 없어지면 그것도 없어지는, 나중에 좀 편리하다고 함
왜냐하면, &str같은걸 쓰면 얘가 살아있는지 죽은지 확인이 어렵기 때문에
&str > String으로 전환하는 것은 "David".to_string() 이런식으로 가능
혹은 String::from("David");
let mut tmp = "David".to_string();
tmp.push('!'); // 근데 char만 받는다는데??
이런식으로 수행 가능
String은 사실 Sized type (Heap에 저장)
str은 dynamic type (Stack에 저장)
String
.capacity
.push
.push_str
.pop
with_capacity (. 없는게 맞음)
.capacity는 Byte크기를 나타내고.
String은 편한데 allocation과 reallocation하는 과정이 있음
let mut tmp = String::with_capacity(26); 이런식으로 reallocation 없도록 allocate를 할 수 있음
let x = 8; // 'let' binding
const는 무조건 어떤 타입인지 써야함, 그리고 이름이 다 대문자가 좋음
const NUMBER: i32 = 20;
static NUMBER: i32 = 0
const를 주로 제일 많이 쓰는데, static은 같은 메모리 공간을 쓰는 보장이 있음
static mut로 할 수 있긴 한데 unsafe함
lifetime이라는 것이 있는데,
static lifetime이라는 것도 있다. 이거는 프로그램 시작 ~ 끝부터 살 수 있는 것
소유권, Ownership
함수내의 이미 사라진 변수에 대해서 return을 하면 이를 compiler가 막음
mutable / unique reference (위험함) (내것을 수정할 수 있음)
imutable / shared reference (내것을 수정할 수 없음)
그러니까, 함수를 쓰는게 남이 하는 거라고생각했을 때, 내거가 안전한지 안한전한지
mutable로 선언한 변수를 reference하려면 mutable로 받아야 한다.
let a = 10;
let a_ref = &a;
let a = 8;
a_ref -> 10, a -> 8
함수에게 입력값으로 a를 넘겨줬을 시에, a의 소유권을 함수에게 넘겨준거임
그래서, a를 다시 못씀 (아얘 사라져버림)
그래서, input을 reference로 넘겨주는것으로 반복사용가능
let a = 10;
print_a(a);
print_a(a);
안됨!
let a = 10;
print_a(&a);
print_a(&a);
됨 :)
input 줄 때
a // value
&a // reference
&mut a // mutable reference
mutable이 약간 좀 많이 짜보면서 느낌을 알아야겠네,
fn add_is_great(mut tmp: String)
fn add_is_great(tmp: &mut String)
Copy 타입을 가진 input의 경우, reference를 추가하는 등의 고려가 필요없다.
"It's trivial to copy the bytes"
Copy 타입이 아닌 input은 함수에게 소유권이 이동해서 다시 사용할 수 없음.
그런데, 이 경우에 .clone()을 통해서 해결할 수가 있음.
let b = "hi".to_string();
print_string(b.clone());
print_string(b);
다만, 메모리 사용량이 더 늘어나겠지. 그래서, reference를 익히는게 더 나은것 같아
rust는 let a; 이런게 안됨 (type 특정이 안되기 때문에)
그리고, rust는 uninitialzed variable을 사용할 수 없음 (당연)
let a = {
let x = 4;
x + 5
};
이런식으로 쓰는게 좀 보통인가봄
loop {
counter += 1;
if counter % 50 == 0 {
break;
}
}
counter
let array = [];
let array = ["One", "Two"]; // [&str; 2]
let array = ["One", "Two", "Five"]; // [&str; 3]
꿀팁 : .method를 이상한 걸 막 쓰면 compiler가 화내면서 type 정보 알려줌
let array = [0; 640]; // 0이 640개
let mut array = [0; 640]; // 변경이 가능한 array
array[0], array[1] 이런식으로 접근 가능하고
array.get(3) 로 좀 더 안전하게 수행할 수 있음.
왜냐하면, 3번이 없을 때 array[3]은 error가 나오는데, get(3)쓰면 none이 나옴
array를 slice하는 것
array[0..2] // 0 이상, 2 미만
array[0..=2] // 0 이상 2 이하
array[..] // 전체
array[3..] // 3 이상
array[..=4] // 4 이하
약간, Array, &str는 간단하고 빠르고
Vec, String은 기능많은 느낌
http://doc.rust-lang.org/std 에서 다양한 standard의 구현을 볼 수 있는데,
String을 보면 [src] 누르고, 사실 Vec[u8]으로 이루어진 것이였음
앞으로의 [는 꺽새 [일 수도 있음 (velog에서 잘 안됨)]]
Vec[String]
String::new()
let mut tmp = Vec::new()
tmp.push(name1);
tmp.push(name2);
tmp.capacity();
약간, String에서 사용할 수 있는 종류랑 비슷함
let my_vec = Vec::from([8, 9, 10]);
근데, Vec보다 더 편리한게 vec!임, 사람들이 더 좋아함
let my_vec = vec![name1, name2];
trait = 초능력
From이 있으면, Into를 쓸 수 있음
let name = String::from("Dave MacLeod");
let city: String = "Seoul".into();
영상 보고는 잘 이해가 안되긴 하네..
위에 String으로 변환하는 동작을 into()로 대신하는 것 같음
() 로 만들 수 있다.
let my_tuple = (8, "Dave MacLeod", vec![8.9.10]);
이런식으로 안에 아무 타입을 넣어도 됨
tuple은 tuple.0, tuple.1, tuple.2 이런식으로 쓰는 것임
Vec[(String, i32)] 이런식으로, Vec안에 tuple을 넣을 수 있음
let my_vec = vec![("Hey", 9), ("Hello there", 91231)] 이런식으로
let tuple = ("one", "two", "three");
let (a, b, c) = tuple;
하면 알아서 3개로 나뉨
이거는 array도 가능함
기억해 array는 [], tuple은 ()
if tmp == 7 {
}
뭐 이런식, 괄호가 굳이 필요없음
if / else if / else
&& = and, || = or
match : switch랑 비슷한데 더 좋음
match tmp {
0 => println!("zero!"),
1 => println!("one!"),
_ => println!("else")
}
마지막 예외처리를 해줘야함 ㅇㅇ
let second = match tmp {
0 => 23,
1 => 45,
_ => 67
};
이렇게도 할 수 있고,
let sky = "cloudy";
let temperature = "warm";
match (sky, temperature) {
("cloudy", "cold") => println!("not that good"),
("clear", "warm") => println!("nice"),
("cloudy", ) => println!("it's cloudy"),
=> println!("I don't know")
}
이거는 위에서 아래로 차례대로 조건문을 보는것이기 때문에, 위에서 이미 선택되면 아래는 보지도 않음
match (children, married) {
(c, m) if m == false => println!("not married"),
(c, m) if c == 0 && m => println!("married"),
}
match rbg {
(r, , ) if r < 10 =>
(, b, ) if b < 10 =>
(, , g) if g < 10 =>
_ =>
}
그리고, 반환값이 같은 type 이여야함. 어떤거는 integer, 어떤거는 string이면 error나옴
match input {
number @ 0..=10 => println!("hihi, {}", number),
_ => println!("nono")
}
이런것처럼 사용할 수 있음
struct → 자기만의 타입을 만들고 싶을때
비슷한건 enum
struct FileDirectory;
fn takes_file_directory(input: FileDirectory) {
println!(~);
}
struct Color(u8, u8, u8);
let my_col = color(20, 50, 100);
접근은 my_col.1, my_col.2, my_col.3 같은거로 해야함
std::~::Display가 없어서, {:?}로 출력해야함
debug print가 유용한 것 같음 (개발자한테)
{:#?}가 있음 → {:?}보다 조금 더 깔끔하게 print
꿀팁! #[derive(Debug)] : 간단하게 이런 기능을 주고 싶다?
// named struct
struct Country {
population: u32,
capital: String,
president: String,
}
let canada = Country {
population: 35_000_000,
capital: "Ottawa".to_string(),
president: "Justin Trudeau".to_string(),
}
접근은 canada.population, canada.capital, canada.president 처럼 할 수 있음
let canada = Country {
population: 35_000_000,
capital: "Ottawa".to_string(),
president: "Justin Trudeau".to_string(),
}
꿀팁! Clippy: 에러 검출 + 코드 잘썼는지 확인해줌
굉장히 툴이 많네, 개좋다!
use std::mem::size_of_val;
println!("{}", size_of_val(&canada));
struct Numbers {
one: u8,
two: u8,
three: u8
four: u32
}
size_of_val(Numbers) = 8이 나옴, 이런식으로 Align 때문에 빈공간을 생성하는데,
Clippy가 이런거를 볼 수 있게 해준다는 건가?
// struct = and 개념
// enum = or 개념, 약간 switch/case 문 같은
enum ThingsInTheSky {
Sun, // 0
Stars // 1
Moon // 2
Plane // 3
}
fn createskystate(time: i32) -> ThingsInTheSky {
match time {
6..=18 => ThingsInTheSky::Sun,
=> ThingsInTheSky::Stars
}
}
fn checkskystate(state: &ThingsInTheSky) {
match state {
ThingsInTheSky::Sun => println!("I can see the sun"),
ThingsInTheSky::Stars => println!("I can see the stars")
=> println!("Nothing in the sky")
}
}
enum ThingsInTheSky {
Sun, // 0
Stars // 1
Moon // 2
Plane // 3
}
use ThingsInTheSky::* 를 쓰게 되면,
ThingsInTheSky::Sun 이렇게 쓸 필요없이,
Sun 이렇게 쓸 수 있음!
use ThingsInTheSky::*
let four_things = vec![Sun, Stars, Moon, Plane];
이런식으로도 가능
enum ThingsInTheSky {
Sun = 0,
Stars = 13,
Moon = 20,
Plane = 43
}
이런식으로 숫자 바꿀 수 있음
숫자 같은 경우에
ThingsInTheSky::Sun as u32 이런식으로 쓰면 바로 숫자로 쓸 수 있음
꿀팁! .is_positive()라는 Method가 있음
enum은 이름과 그에 따른 값을 같이 넣어줄 수 있음
enum Number {
U32(u32)
I32(i32)
}
loop {
}
위에는 While(true) 처럼 계속 반복
break로 중간 해제 가능
꿀팁 : 'first_loop: loop 이런식으로 이름을 붙일 수 있음. 이를 tick이라고 함
근데, 이걸로 한번에 break 할 수 있음!!
'first_loop: loop {
'second_loop: loop {
println!("이런식으로!");
break 'first_loop;
}
}
for num in 0..=3 {
}
처럼 python같이 for문 돌릴 수 있음 끝!
loop를 통해서 어떤 값을 설정할 수도 있음
break counter로 loop의 값 설정가능
let num = loop {
counter += 1;
if counter == 3 {
break counter;
}
}
struct, enum 같은 거는 .len()과 같은 게 없을 수 있는데
이를 구현하는 방법
. #[derive(Debug)] // debug print 선언
struct Animal {
age: u8,
animal_type: AnimalType
}
. #[derive(Debug)]
enum AnimalType {
Cat,
Dog
}
impl Animal {
fn new(age: u8) -> Self { // 굳이 이름이 new가 아니여도 됨, Self나 Animal이나 같음
Self {
age,
animal_type: AnimalType::Cat
}
}
}
fn main() {
let my_animal = Animal::new(10);
}
아래 안에다가 구현할 함수들을 엄청 많이 정의 가능
impl Animal {
fn new_cat() -> Self {
}
fn print(&self) {
println!("I am a: {:?}", self);
}
}
my_animal.print(); 혹은
Animal::print(&my_animal); // 사실 위에꺼를 Compiler는 이것으로 사용함
enum도 input으로 활용이 가능하다~
enum(String) 처럼 쓰는 활용도 가능하다.
let John = Person {
name: "John",
height: 178,
happiness: flase,
}
// destructuring
let Person {
name: a,
height: b,
happiness: c
} = John;
이런식으로 destructuring을 수행할 수 있음
let Person {
name,
height,
..
} = John;
이런식도 가능 (..)은 여러개 무시
아하, 그 &, 에 대한거임
a = 10
(&a) = 10
끝
반대 : concrete
Generics : 일반적인
generics → T로 표현
fn give_thing<T.>(input: T) -> T {
input
}
// 굳이 T가 아니여도 되긴 함, 막 GenericType 이런 단어여도 됨
use std::fmt::Display;
fn give_thing<T: Display>(input: T) -> T {
input
}
use std::fmt::Display;
use std::cmp::PartialOrd
fn compare_and_print<T: Display, U: Display + PartialOrd>(statement T, num_1: U, num_2: U) {
println!(~~~)
}
fn compare_and_print<T, U>(statement T, num_1: U, num_2: U)
where
T: Display,
U: Display + PartialOrd,
{
println!(~~~)
}