함수와 같은 형태를 갖고 있지만 특정 객체 안에 정의된다. 이 경우 첫번째 파라미터는 객체의 인스턴스로 self를 받게 되는데 인자로 self, &self 중 어떤 것을 선택하느냐에 따라 인스턴스의 전달 방법이 달라진다. 4.2절 참조자와 빌림에서 언급했던 것 처럼 self를 인자로 사용하면 인스턴스의 소유권이 메소드 안쪽으로 이동하게 된다. 대부분의 경우 &self를 사용해서 인스턴스의 참조자를 넘겨 사용한다.
구조체에 사용하며, 구조체의 출력 부분을 실제 구현하지 않았어도 디버깅을 위해서 구조체 내용을 출력해 볼 수 있도록 해준다.
#[derive(Debug)]
struct Rectangle {
length: u32,
width: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.length * self.width
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.length > other.length && self.width > other.width
}
}
{
let rect1 = Rectangle { length: 50, width: 30 };
let rect2 = Rectangle { length: 40, width: 10 };
let rect3 = Rectangle { length: 45, width: 60 };
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
dbg!(&rect1); //rect1으로 넘기게 되면 소유권이 넘어가서 아래 프린트문에서 빈 변수를 호출하는 일이 발생하여 에러 방생.
println!("{:#?}", rect1);
rect1
}
[src/lib.rs:58] &rect1 = Rectangle { length: 50, width: 30, }
The area of the rectangle is 1500 square pixels. Can rect1 hold rect2? true Can rect1 hold rect3? false Rectangle { length: 50, width: 30, }
Rectangle { length: 50, width: 30 }
C++ 같은 언어에서는 메소드 호출을 위해서 ., -> 연산자를 구분하여 사용한다. 객체 메소드를 직접 호출하는 경우 .을 이용하고, 객체의 포인터에서 메소드를 호출할 경우 ->를 사용한다.(역참조)
러스트에서는 자동 참조 및 역참조(Automatic referencing and dereferencing) 기능을 갖고 있어서 참조 및 역참조에 대해서 자동으로 처리해 주기 때문에 ., ->를 구분하여 사용할 필요가 없기 때문에 -> 연산자가 존재하지 않는다.
{
let rect1 = Rectangle { length: 50, width: 30 };
let r = &rect1;
println!("{} {}", (*r).width, r.width);
};
30 30
self 파라미터를 갖지 않는 함수, 인스턴스의 데이터를 사용하지 않는 함수지만 네임스페이스로 묶어서 연관성을 부여하고자 할때 사용한다.
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { length: size, width: size }
}
}
{
let rect1 = Rectangle::square(35);
let r = &rect1;
println!("{} {}", (*r).width, r.length);
};
35 35
커스텀 데이터형으로 열거된 데이터 중 하나를 선택한다는 점에서 구조체와 다르다. 구조체는 인스턴스를 생성하면 구조체에 정의된 모든 데이터들이 생성되지만 열거형은 선택된 하나만 생성된다. 이렇게 열거된 자료형들을 variants라고 한다.
열거형은 특정한 의미와 데이터형을 갖지 않고 그냥 열거된 자료형 자체를 사용할 수도 있고 특정 데이터를 포함할 수도 있다.
아래 예제는 IP 주소 버전을 열거하여 사용하는 예이다. IP 주소는 V4, V6 중 하나를 갖게 된다. 구조체를 사용해서 IP 버전과 해당 IP 주소를 저장하는 자료형을 정의하였다.
#![allow(unused)]
#[derive(Debug)]
enum IpAddrKind {
V4,
V6,
}
#[derive(Debug)]
struct IpAddr {
kind: IpAddrKind,
address: String,
}
{
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
println!("{:#?}", home);
println!("{:#?}", loopback);
};
IpAddr { kind: V4, address: "127.0.0.1", } IpAddr { kind: V6, address: "::1", }
구조체를 사용하지 않고 열거형 variant에 직접 데이터를 붙일 수 있다.
#[derive(Debug)]
enum IpAddr {
V4(String),
V6(String),
}
{
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
println!("{:#?}", home);
println!("{:#?}", loopback);
};
V4( "127.0.0.1", ) V6( "::1", )
#[derive(Debug)]
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
{
let home = IpAddr::V4(127,0,0,1);
let loopback = IpAddr::V6(String::from("::1"));
println!("{:#?}", home);
println!("{:#?}", loopback);
};
V4( 127, 0, 0, 1, ) V6( "::1", )
열거형 Variant에는 어떤 종류의 데이터라도 넣을 수 있습니다. 기본 자료형뿐만 구조체 및 열거형도 넣을 수 있다.
struct Ipv4Addr {
// details elided
}
struct Ipv6Addr {
// details elided
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
아래와 같이 열거형을 정의하게되면 여러 개의 구조체를 정의하고 Message 타입으로 그룹화한 것과 같다.
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
struct QuitMessage; // 유닛 구조체
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // 튜플 구조체
struct ChangeColorMessage(i32, i32, i32); // 튜플 구조체
RUST에서는 값이 없음을 표현하는 Null을 갖고 있지 않다. 그렇다고 해서 null 표현을 못하는 것은 아니다. 값의 존재 혹은 부재의 개념을 표현할 수 있는 Option
Option
None을 사용할 때는 Option
#[derive(Debug)]
enum Option<T> {
Some(T),
None,
}
{
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: std::option::Option<i32> = None;
println!("{:#?}", some_number);
println!("{:#?}", some_string);
println!("{:#?}", absent_number);
}
Some( 5, ) Some( "a string", ) None
()
c언어의 switch case문과 유사한 연산자를 제공한다. 열거형을 공부하고 나면 뭔가 좋은데 사용하기가 힘들었다. match는 열거형을 사용하기 위한 필수적인 연산자이지만 열거형만을 위한 것은 아니다. 간단한 예로 열거형의 variant를 패턴으로 사용하는 match 표현식을 알아보자.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
println!("Penny : {}\nNickel : {}\nDime : {}\nQuarter : {}",
value_in_cents(Coin::Penny),
value_in_cents(Coin::Nickel),
value_in_cents(Coin::Dime),
value_in_cents(Coin::Quarter));
Penny : 1 Nickel : 5 Dime : 10 Quarter : 25
앞에서 함수를 구현한 것을 열거형 Coin의 메소드로 구현할 수도 있다.
impl Coin
{
fn value(&self) -> u32
{
match self {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
let p = Coin::Penny;
p.value()
1
열거형 variant는 내부에 값을 바인딩하여 사용할 수도 있다. 이 경우의 처리에 대해서 알아보자
enum UsState {
Alabama,
Alaska,
// ... etc
}
enum Coin2 {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
impl UsState {
fn value(&self) -> String {
match self {
UsState::Alabama => String::from("Alabama"),
UsState::Alaska => String::from("Alaska"),
}
}
}
impl Coin2{
fn value(&self) -> u32 {
match self {
Coin2::Penny => 1,
Coin2::Nickel => 5,
Coin2::Dime => 10,
Coin2::Quarter(state) => {
println!("State quarter from {:?}!", state.value());
25
},
}
}
}
let a = Coin2::Quarter(UsState::Alabama);
a.value()
State quarter from "Alabama"!
25
Option
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
fn some_value(x: Option<i32>) -> i32{
match x
{
None => 0,
Some(i) => i,
}
}
println!("{} {} {}",some_value(five),some_value(six),some_value(none));
5 6 0
match는 모든 패턴에 대해서 빠짐 없이 정의해줘야 한다. 그렇지 않으면 Rust는 컴파일 단계에서 에러를 줄 것이다. 하지만 모든 경우에 대해서 관심을 갖고 있지 않을 수도 있다. 이런 경우 사용할 수 있는 특별한 패턴인 _ placeholder를 이용한다.
fn some_u8_value(x:u8){
match x {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
}
some_u8_value(1);
some_u8_value(2);
some_u8_value(3);
some_u8_value(4);
some_u8_value(5);
some_u8_value(6);
some_u8_value(7);
some_u8_value(8);
one three five seven
if net 문법은 match에서 다뤘던 패턴들 중 어느 하나만을 정의하고 싶을때 사용한다. 비교하려는 패턴이 무엇이던 상관 없이 if net a = b {...} 형식으로 비교하려는 패턴을 넣으면 된다.
let a = 1;
if let 1 = a {
println!{"Partten is 1"};
}
Partten is 1
()
let some_u8_value = Some(3);
if let Some(3) = some_u8_value {
println!("three");
}
three
()