두 정수 A와 B를 입력받은 다음, A×B를 출력하는 프로그램을 작성하시오.
첫째 줄에 A와 B가 주어진다. (0 < A, B < 10)
첫째 줄에 A×B를 출력한다.
1 2
2
3 4
12
use std::io;
fn main() {
println!("Please enter two numbers: ");
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let numbers: Vec<i32> = input
.split_whitespace()
.map(|s| s.parse().unwrap())
.collect();
let a:i32 = numbers[0];
let b:i32 = numbers[1];
println!("{}", a * b);
}
use std::io;
fn main() {
println!("Please enter two numbers: ");
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let mut numbers = input.split_whitespace();
let a: i32 = numbers.next().unwrap().parse().unwrap();
let b: i32 = numbers.next().unwrap().parse().unwrap();
println!("{}", a * b);
}
case-1
코드와 유사하지만, 입력을 처리하는 방식에 약간의 차이가 있습니다. 이 버전에서는 split_whitespace()
로 분리한 숫자
들을 numbers 이터레이터
에 저장
한 뒤, next() 메서드
를 사용하여 각 숫자를 차례대로 가져와
서 a와 b에 할당
합니다. 이후 곱셈 결과를 출력합니다.
next()
메서드 : Option<T>
타입을 반환None을 반환
Some(T)를 반환
합니다. 이를 통해 이터레이터가 더 이상 요소를 가지고 있지 않음을 알 수 있습니다.Option<T>
타입을 반환 -> None
or Some(T)
일수 있음.unwrap메서드
:
2.1. Option 타입:
2.2. Result 타입:
2.3. unwrap() 메서드:
2.4. 안전한 에러 처리:
use std::io;
fn main() {
println!("Please enter two numbers: ");
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let numbers: Vec<i32> = input
.split_whitespace()
.map(|s| s.parse().unwrap())
.collect();
let (a, b) = (numbers[0], numbers[1]);
println!("{}", a * b);
}
destructuring or pattern matching
destructuring
을 쓸수 있는 데이터 타입.
2.1. 튜플: 여러 개의 다른 타입의 값을 묶어 하나로 만든 데이터 구조
2.2. 배열: 동일한 타입의 여러 개의 값을 묶어 하나로 만든 데이터 구조
2.3. Structs: 용자 정의 데이터 타입으로, 여러 개의 다른 타입의 값을 묶어 하나로 만든 데이터 구조
2.4. Enums: 여러 개의 다른 타입의 값을 묶어 하나로 만든 데이터 구조로, 각각의 값은 Enum의 다른 variant에 해당합니다.
destructuring
예시 코드
3.1. 튜플 destructuring
fn main() {
let tuple = (1, "hello", 3.14);
let (a, b, c) = tuple;
println!("{}, {}, {}", a, b, c); // 출력: 1, hello, 3.14
}
3.2. 배열 destructuring
fn main() {
let array = [1, 2, 3];
let [a, b, c] = array;
println!("{}, {}, {}", a, b, c); // 출력: 1, 2, 3
}
3.3. Struct
destructuring
struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 3, y: 4 };
let Point { x, y } = point;
println!("{}, {}", x, y); // 출력: 3, 4
}
3.4. Enum
destructuring
enum Animal {
Dog { name: String, age: i32 },
Cat { name: String, age: i32 },
}
fn main() {
let animal = Animal::Dog {
name: String::from("Buddy"),
age: 3,
};
match animal {
Animal::Dog { name, age } => println!("Dog named {} is {} years old", name, age),
Animal::Cat { name, age } => println!("Cat named {} is {} years old", name, age),
}
}
enum
, 열거형?4.1. 구조
enum EnumName {
Variant1(Type1),
Variant2(Type2),
Variant3(Type3),
// ...
}
enum Shape {
Circle(f64), // 원
Rectangle(f64, f64), // 사각형
Square(f64), // 정사각형
}
이 열거형에서 Shape는 열거형의 이름이며, Circle, Rectangle, Square는 각각의 variant입니다. 각 variant는 다른 타입의 값을 가집니다. 원은 반지름을 나타내는 f64 타입의 값을 가지며, 사각형은 가로와 세로 길이를 나타내는 두 개의 f64 타입 값을 가집니다. 정사각형은 한 변의 길이를 나타내는 f64 타입 값을 가집니다.
예. 아래는 Shape 열거형의 Rectangle variant를 사용하는 코드 예시입니다. Shape 열거형을 선언하고, 함수를 사용하여 사각형의 면적을 계산하는 예제입니다.
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Square(f64),
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
Shape::Rectangle(width, height) => width * height,
Shape::Square(side) => side * side,
}
}
fn main() {
let rectangle = Shape::Rectangle(4.0, 6.0);
let rectangle_area = area(&rectangle);
println!("The area of the rectangle is: {:.2}", rectangle_area);
}
use std::io;
use std::str::FromStr;
fn main() {
println!("Please enter two numbers: ");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read line");
let mut numbers = input.split_whitespace().map(|s| {
i32::from_str(s).unwrap_or_else(|_| {
eprintln!("Error: Invalid input");
std::process::exit(1);
})
});
let a = numbers.next().expect("Missing number");
let b = numbers.next().expect("Missing number");
println!("{}", a * b);
}
parse()
대신 from_str()
을 사용하여 에러 메시지를 표시하고 프로그램을 종료하는 처리를 추가했습니다. 또한, 입력 문자열을 공백으로 분리하고 각각의 숫자를 추출하는 과정을 조금 더 명확하게 표현하였습니다.fn main() {
let (a, b): (i32, i32);
std::io::stdin().read_line(&mut String::new()).map(|s| s.trim().parse().unwrap()).collect();
println!("{}", a * b);
}
use std::io;
fn main() {
println!("Please enter 2 numbers separated by a space:");
let input = read_input();
let parse_input(&input);
if let Some((a, b)) = numbers {
println!("the product of the 2 numbers is: {}", a * b);
} else {
eprintln!("Error: Invalid input");
}
}
fn read_input()-> String {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read line from stdin");
input
}
fn parse_input(input: &str) {
let mut numbers = input.split_whitespace().map(|s| i32::from_str(s));
match (numbers.next(), numbers.next()) {
(Some(Ok(a)), Some(Ok(b))) => Some((a, b)),
_ => None,
}
}
let input = read_input();
: read_input() 함수를 호출하여 사용자로부터 입력을 받아 변수 input에 저장합니다.
let parse_input(&input);
: parse_input() 함수를 호출하고 input 변수를 인자로 전달합니다. parse_input() 함수는 숫자 두 개를 반환합니다.
if let Some((a, b)) = numbers { ... }
: parse_input() 함수에서 반환한 숫자들이 유효한지 확인하고, 유효한 경우 두 숫자를 곱한 결과를 출력합니다.
else { eprintln!("Error: Invalid input"); }
: parse_input() 함수에서 반환한 숫자들이 유효하지 않은 경우, 에러 메시지를 출력합니다.
fn read_input()-> String { ... }
: 사용자로부터 입력을 받기 위한 함수입니다. io::stdin().read_line() 메소드를 사용하여 콘솔에서 입력을 받습니다.
fn parse_input(input: &str) { ... }
: 사용자 입력 문자열을 받아 숫자를 추출하고, 추출한 숫자를 반환하는 함수입니다.
input.split_whitespace().map(...)
메소드를 사용하여 입력 문자열에서 공백으로 구분된 숫자들을 추출합니다. 추출한 숫자를 정수로 변환한 후, (a, b) 형태의 튜플로 반환합니다.
parse_input
:
7.1. 매개변수 :
input: &str
:
매개변수 input의 원형 타입은 String
입니다. 하지만 &String
과 같은 문자열 레퍼런스는 문자열슬라이스(&str)과 호환됨. 따라서 &String
를 &str
로 변환할 때는 별도의 변환 과정이 필요하지 않습니다. 이를 더 일반화해서 말하면, Rust에서는 "Deref coercion"
이라는 기능을 제공하는데, 이를 통해 컴파일러가 암시적으로 타입을 변환해줍니다.
Deref coercion
을 한글로는 "역참조 강제 변환"
또는 "간접 참조 강제 변환"
으로 번역할 수 있습니다. "Deref"는 "역참조"를 의미
하는데, 이는 포인터나 레퍼런스 타입의 값을 직접 참조할 때 사용하는 연산자인 *와 유사
한 개념입니다. 따라서 "Deref coercion"는 암시적으로 역참조 연산을 수행
하여 타입을 변환
하는 것을 의미합니다. input: &String
도 가능
Q. parse_input
메서드의 매개변수 타입으로 input: &String 도 가능하다. 그렇다면 &str, &String 어느것을 쓰는것이 더 효율적
?
A. 실제 메모리 상에서는 &str이 더 효율적
입니다. 이는 &String을 사용하면 추가적인 메모리 할당과 복사가 발생
하기 때문입니다. 따라서 가능하다면, 함수의 매개변수나 구조체의 필드 등
에서 문자열을 참조하는 경우
에는 &str을 사용하는 것이 좋습니다
.
(numbers.next(), numbers.next())
튜플_
와일드카드 패턴 : 모든 값과 매치된다는 것
패턴 매칭에서 해당 패턴과 일치하는 값이 없는 경우를 처리
마지막 분기에 사용
되며, 일치하는 패턴이 없는 경우
디폴트 값
을 지정할 때 유용map메서드 클로저
|s| i32::from_str(s)
에서 |s|
는 클로저의 인자를 정의
하고, i32::from_str(s)
는 클로저의 반환값
을 정의합니다. map(|s| i32::from_str(s))
는 각각의 문자열에 대해 i32::from_str
함수를 호출하여 결과 값을 반환하는 클로저를 생성하고, 이 클로저를 각각의 문자열에 적용하여 새로운 Iterator 를 생성
합니다.알아야 하는 이유: 자동으로 형 변환이 가능하기 때문
Rust에서 Deref coercion은 Deref 트레이트를 구현하는 타입에서 사용됩니다.
Deref 트레이트는 *
연산자의 동작을 오버로드하는데 사용됩니다. 이를 통해 해당 타입에 대한 참조가 아닌 해당 타입 자체에 대한 작업을 수행
할 수 있습니다.
Deref coercion
은 Deref 트레이트를 구현하는 모든 타입에서 사용할 수 있습니다
. 일반적으로 Box, Vec, String, Rc, Arc와 같은 힙 할당 타입
에서 많이 사용됩니다. 또한, 참조자(&) 타입
에 대해서도 사용할 수 있습니다.
let boxed_num: Box<i32> = Box::new(42);
let num_ref: &i32 = &boxed_num;
assert_eq!(*num_ref, 42);
let vec_of_strings = vec!["hello", "world"];
let slice_of_strings: &[&str] = &vec_of_strings;
assert_eq!(slice_of_strings[0], "hello");
let s = String::from("hello");
let s_ref: &str = &s;
assert_eq!(s_ref, "hello");
use std::rc::Rc;
let rc_num: Rc<i32> = Rc::new(42);
let num_ref: &i32 = &rc_num;
assert_eq!(*num_ref, 42);
use std::sync::Arc;
let arc_num: Arc<i32> = Arc::new(42);
let num_ref: &i32 = &arc_num;
assert_eq!(*num_ref, 42);
힙 할당 타입은 메모리 할당이 동적으로 이루어지는 타입을 의미합니다. 러스트에서는 힙에 메모리를 할당하는 타입으로 Box, Vec, String, Rc, Arc
등이 있습니다. 이러한 타입은 값이 스택이 아닌 힙에 저장되기 때문에 포인터로 스택에 참조
됩니다.
참조자(&) 타입
은 다른 값을 참조
하는 데 사용
되는 타입
으로, 해당 값을 소유하지 않습니다
. 대신 참조된 값의 메모리 위치를 가리키는 포인터 역할
을 합니다. 이러한 타입은 불변 참조자(&T)
와 가변 참조자(&mut T)
가 있습니다. 불변 참조자는 값을 읽을 수만 있으며 변경할 수 없고, 가변 참조자는 값을 읽고 변경할 수 있습니다. 참조자는 스택에 저장
되며, 스택에서 값을 참조
하기 때문에 값이 소유한 메모리 공간의 크기에 영향을 받지 않습니다.
2.1. Q. 참조자 타입을 사용 할 수 없는 타입
은 뭐야? 혹은 사용 할 수 없는 경우
가 있어?
2.1. A. 참조자(&)는 소유권을 빌릴 수 있는 타입
에서만 사용할 수 있습니다.
2.1.1. 소유권을 가지지 않는 타입에서는 참조자를 사용할 수 없습니다.
i32, u8, f64 등), 불(bool) 타입, 복합 타입 중 하나 이상의 필드가 소유권을 가지는 타입(
Box, Vec, String` 등)이 포함됩니다.