다른 모듈 접근하기



모듈이든 함수든, '::' 를 통해 계층 이동을 수행함.


pub mod a {
pub mod series {
pub mod of {
pub fn nested_modules() {}
}
}
}

a::series::of::nested_modules();



use 키워드 사용하기


use 키워드를 사용하면 접근을 보다 편하게 할 수 있음.


use a::series::of;

of::nested_modules();


use a::series::of; 는 of 모듈을 사용하겠다는 의미임. 이를 통해 of 모듈에 접근할 수 있음.


use 키워드는 정확히 지정된 그 항목만을 가지고 옴: 그 모듈의 자식까지 가지고 오진 않기 때문에, 아래와 같이 사용할 수는 없음.


use a::series;

of::nested_modules(); // 에러!



of 모듈은 series 모듈의 자식 모듈이지만, use 로 지정된 것은 어디까지나 series 모듈임. 때문에 이 경우 아래와 같이 사용해야 함.


use a::series;

series::of::nested_modules(); // OK



특정 함수만을 가지고 올 수도 있음.


use a::series::of::nested_modules;

nested_modules(); // OK



enum 도 모듈과 같은 하나의 형태를 가지기 때문에, enum 타입을 가지고 오는 일도 가능함. 그것도, enum 타입의 특정 항목만을 가지고 올 수도 있음.


enum TrafficLight {
Red,
Yellow,
Green,
}

use TrafficLight::{Red, Yellow};

fn main() {
let red = Red;
let yellow = Yellow;
let green = TrafficLight::Green;
}


같은 TrafficLight 타입이라도, Red, Yellow 는 use 를 통해 불러왔기에 바로 접근 가능하지만, Green 의 경우 TrafficLight::Green 의 방식으로 접근해야 함.


모든 enum 항목을 나열하는 일은 번거롭기에, 아래와 같은 처리도 가능.


use TrafficLight::*;

이제 TrafficLight 의 모든 항목을 바로 접근할 수 있음.



super 키워드 사용하기


다른 스코프의 항목 호출 시, 맨 앞에 '::' 을 사용함은 루트에서부터 접근함을 의미함.


::a::series::of::nested_modules();    // 루트 경로의 a 모듈에서부터 접근



파일 시스템에서 루트로 접근할 때와 같이, 루트를 통해 모든 모듈의 경로를 찾아갈 수 있지만, 때로는 상대경로가 편함. super 키워드를 사용하면 바로 위의 부모 모듈에 접근할 수 있음.


super::super::some_function();    // 2단계 상위 모듈의 some_function 호출




'Rust' 카테고리의 다른 글

컬렉션(스트링)  (0) 2018.02.24
컬렉션(벡터)  (0) 2018.02.24
모듈(pub)  (0) 2018.02.22
모듈  (0) 2018.02.22
Enum(if let)  (0) 2018.02.21

Public - Private



자바의 접근제한자처럼, 러스트에도 접근제한자가 존재함. 기본적으로 러스트의 모든 접근제한자는 private 임. pub 키워드를 사용하여 이를 퍼블릭으로 바꿀 수 있음.


러스트의 privacy 규칙은 다음과 같음.

  • 어떤 항목이 public 이면, 이 항목의 어떠한 부모 모듈을 통해서든 이 항목에 접근 가능.
  • 어떤 항목이 private 이면, 오직 이 항목의 직계 부모 모듈, 혹은 직계 부모 모듈의 자식 모듈(형제 or 형제의 자식) 모두 이 항목에 접근 가능.
여기서 혼동하지 말 것! 항목이 모듈이면 부모 모듈은 자신이 속한 모듈이지만, 항목이 함수이면 부모 모듈은 함수가 속한(자신이 정의된) 모듈이 됨.


'Rust' 카테고리의 다른 글

컬렉션(벡터)  (0) 2018.02.24
모듈(다른 모듈 접근)  (0) 2018.02.22
모듈  (0) 2018.02.22
Enum(if let)  (0) 2018.02.21
Enum(match)  (0) 2018.02.21

모듈의 기본



아래 cargo 명령어는 실행용 프로젝트를 생성함


cargo new communicator --bin 


아래와 같이 --bin 을 빼면 라이브러리 프로젝트를 생성함


cargo new communicator


실행용 프로젝트에 main.rs 가 존재한다면, 라이브러리 프로젝트에는 lib.rs 가 존재함 각각 각 프로젝트의 메인 파일임.


아래 코드는 network 라는 모듈을 생성함.


mod network {
fn connect() {
}
}


같은 파일(현재는 lib.rs)에 몇 개의 모듈이든 둘 수 있음.


mod network {
fn connect() {
}
}

mod client {
fn connect() {
}
}


당연하게도, 각 connect 함수는 서로 다른 범위에 존재하기에, 충돌은 없음. 각각 network::connect() / client::connect() 로 호출됨.


한 모듈은 자신의 서브모듈(자식모듈)을 얼마든지 가질 수 있음.


mod network {
fn connect() {
}

mod client {
fn connect() {
}
}
}


이제 connect 함수는 각각 network::connect() / network::client::connect() 로 호출됨.



다른 파일로 모듈 이동하기


모듈 파일 규칙은 다음과 같음.

  • 분리하려는 모듈, foo 가 있고, foo 에 서브모듈이 없을 경우, foo 가 선언된 같은 경로에 foo.rs 파일을 만들면 됨.
  • 분리하려는 모듈, foo 가 있고, foo 에 서브모듈이 있을 경우, foo 디렉토리를 만들고 mod.rs 파일을 만들면 됨. foo 모듈 본문은 mod.rs 에 작성.


아래 코드가 있음(lib.rs). 여기서 client 모듈을 분리할 것.


mod client {
fn connect() {
}
}

mod network {
fn connect() {
}

mod server {
fn connect() {
}
}
}


client 는 선언부만 남김. 


mod client;

mod network {
fn connect() {
}

mod server {
fn connect() {
}
}
}


그리고 같은 경로(client 가 lib.rs 에 선언되었으니 root 경로)에 client.rs 파일을 만들어 여기에 본문을 작성


fn connect() {
}


모듈 파일에 다시 mod 를 쓸 필요는 없음. mod 를 또 쓸 경우, 여기서 또다른 서브모듈을 선언하는 셈이 됨.


그리고 network 모듈도 분리하자면, lib.rs 를 다음과 같이 수정.


mod client;

mod network;


network 모듈은 server 라는 서브모듈을 가지고 있음. 규칙에 따라, network 라는 디렉토리를 만들어 그 안에 mod.rs 파일을 만들고 network 모듈의 본문을 그대로 복사. network/mod.rs 파일 내용은 다음과 같음.


fn connect() {
}

mod server {
fn connect() {
}
}


여기서 또 server 모듈을 분리자면, network/mod.rs 파일을 다음과 같이 수정.


fn connect() {
}

mod server;


그리고 mod.rs 의 경로(network 디렉토리) 에 server.rs 파일을 만들고 여기에 본문을 그대로 복사. network/server.rs 파일 내용은 다음과 같음.


fn connect() {
}


클리어.


이제 모듈 구조는 다음과 같음.



모듈의 파일 구조는 다음과 같음.




'Rust' 카테고리의 다른 글

모듈(다른 모듈 접근)  (0) 2018.02.22
모듈(pub)  (0) 2018.02.22
Enum(if let)  (0) 2018.02.21
Enum(match)  (0) 2018.02.21
Enum(Option<T>)  (0) 2018.02.21

Using if let with Enum



match 가 너무 장황하게 느껴질 때, if let 은 보다 간단한 옵션이 됨.


let some_u8_value = Some(0u8);

match some_u8_value {
Some(3) => println!("three"),
_ => (),
}


위 match 는 아래 if let 과 동일하게 작동함.


if let Some(3) = some_u8_value {
println!("three");
}


else 도 가능.


if let Some(3) = some_u8_value {
println!("three");
} else {
println!("not three");
}


if let 은 match 와 비슷함. 그러나, 그 코드는 match 보다 짧고, 형식적인 코드가 보다 적음. 그리고 중요한 것: if let 은 비교 패턴을 원하는 대로 정할 수 있음. match 는 반드시 enum 타입의 모든 패턴을 다루어야 하지만, if let 은 그런 거 없음. 비교하고 싶은 패턴이 하나면 하나, 둘이면 둘, 원하는 대로 가능함. 비교하고 싶은 값이 적을 때는 확실히 if let 이 편할 것임. 그러나 여기에는 match 에서의 안정성이 없다는 것을 유념해야 함.




'Rust' 카테고리의 다른 글

모듈(pub)  (0) 2018.02.22
모듈  (0) 2018.02.22
Enum(match)  (0) 2018.02.21
Enum(Option<T>)  (0) 2018.02.21
Enum  (0) 2018.02.21

Using Match with Enum



match 는 다른 언어의 switch 와 비슷함. 대상을 지정해놓고 대상의 값이 될 수 있는 목록을 나열하고 매칭되는 값에 해당하는 코드를 실행함. 그 대상은 리리터럴, 변수, 와일드카드 등 여러가지가 될 수 있는데, 여기서는 enum 과 함께 사용하는 법을 다룸.


enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}


위의 match 는 value_in_cents 의 파라미터로 넘겨받은 enum 에 매칭되는 값을 값을 반환함. if 와 비슷하지만, if 의 조건절에는 Boolean 값만이 올 수 있지만 match 에는 어떠한 타입도 사용 가능하다는 차이가 있음.


문법적으로, match 에 나열된 비교 대상을 패턴이라 칭함. 비교는 작성된 순서(위에서 아래)로 이루어지며, 패턴 매칭이 발생하면 나머지 패턴 비교는 수행하지 않음. 


위의 Penny 에서 처럼, 실행될 코드가 두 줄 이상이면 중괄호를 써서 줄을 나눔.


match 는 철저함. enum 타입으로 비교할 때, 이 타입의 모든 패턴을 다루어야 함. 해당 타입에 빼놓은 패턴이 있다면 이는 컴파일 에러를 일으킴. 아래는 그 예:


enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,

}
}

이처럼 match 로 Coin 타입을 다루면서 Quarter 를 빼놓으면 컴파일 에러!



플레이스홀더 _


match 는 편의상 플레이스홀더를 제공함. 이는 다른 언어의 switch 에 있는 default 라 보면 됨. 패턴 비교를 하다가 '여기부턴 나머지 모두 똑같이' 를 구현하고 싶을 때 사용. 패턴에 '_' 를 사용하면 됨.


let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("two"),
5 => println!("five"),
7 => println!("seven"),
_ => ();
}


1 - 3 - 5 - 7 까지 비교하여 매칭되지 않으면 모두 () 를 반환.



값을 지닌 enum 패턴


enum 은 안에 자신의 값을 가질 수 있다고 했음. 아래 코드는 이 값이 match 와 어떤식으로 사용될 수 있는지 보여줌.


enum UsState {
Alabama,
Alaska,
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}


Coin::Quarter 패턴에서 보듯, enum 의 값을 실행 코드에서 사용할 수 있음. Coin::Quarter 가 파라미터로 주어지면, 각 패턴을 위에서부터 순서대로 비교하다가 Quarter 에서 매칭이 발생하고, Quarter 의 값, UsState 가 패턴 안의 state 에 바인딩 되고, 이 state 를 매칭 코드에서 사용할 수 있게 됨.



Option<T>


이전에 다룬 Option<T> 에서 Some 값을 사용할 수 있음. 


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);


Coin::Quarter 에서와 같음. plus_one 함수에서 Some 값이 i 에 바인딩되어 + 1 한 값이 반환됨. 물론 값 그대로가 아닌, Some(i + 1) 의 형태로. None 의 경우 None 이 반환됨. None 도 Option<T> 타입이기 때문에 반환될 수 있음.







'Rust' 카테고리의 다른 글

모듈  (0) 2018.02.22
Enum(if let)  (0) 2018.02.21
Enum(Option<T>)  (0) 2018.02.21
Enum  (0) 2018.02.21
구조체(메서드)  (0) 2018.02.20

Option<T>


러스트에는 기본적으로 null 이 없음. 때문에 널포인터 참조와 같은 에러는 발생하지 않음. 하지만, '값이 없음'을 표현하는 방법이 필요할 때가 있음. 그럴 때 Option<T> 이라는 enum 타입을 사용할 수 있음.


Option<T> 은 표준 라이브러리에 아래와 같이 정의되어 있음


enum Option<T> {
Some(T),
None,
}

T 는 제너릭.


Some 은 값이 있음을 나타내며, None 은 값이 없음을 나타냄. 이 두 '상태' 를 가지고 null 체크와 같은 일을 수행할 수 있음. Option 은 기본적으로 스코프에 포함되어, 명시적으로 로드하지 않아도 사용할 수 있음.


Option<T> 은 아래와 같이 선언됨.


let some_number = Some(5);
let some_string = Some("a string");

let absent_number: Option<i32> = None;


타입 추론에 따라 some_number 는 Option<i32> 타입, some_string 은 Option<&str> 이 됨. 그런데 absent_number 에만 타입을 지정한 이유는, None 은 값을 가지지 않기 때문에 타입을 지정하지 않으면 absent_number 의 타입을 추론할 수 없어 컴파일 에러가 발생함. 때문에 None 은 타입이 필요함.


말했듯이, Some 은 값이 있는 상태를, None 은 null 을 의미함. 하지만, 그렇다고 해서 Some 을 값 자체로 사용할 수는 없음. Some 은 어쨌든 Option 이라는 enum 타입일 뿐임. 아래와 같은 연산은 컴파일 에러를 일으킴. 인티저 타입에 Option 타입을 더할 수는 없기 때문. Some 이 값이 아니라는 걸 기억해야 함.


let x = 5;
let y = Some(5);

let sum = x + y;


5 라는 값이 있을 때, 이 값은 분명히 존재하는 값이며, 러스트는 이 값이 유효한 값인지 아닌지 체크할 필요가 없음. 그런데 Option 타입의 경우, 값이 존재할 수도(Some), 존재하지 않을 수도(None) 있음. 때문에 Option 을 사용하기 위해서는 먼저 이게 Some 인지 None 인지 체크할 필요가 있음. 그리고 우리는 Some 일 경우의 동작과, None 일 경우의 동작을 모두 코드로 작성할 수 있음. 이는 match 를 이용함.




'Rust' 카테고리의 다른 글

Enum(if let)  (0) 2018.02.21
Enum(match)  (0) 2018.02.21
Enum  (0) 2018.02.21
구조체(메서드)  (0) 2018.02.20
구조체(활용)  (0) 2018.02.20

Enum



enum 정의


enum IpaddrKind {
V4,
V6
}


enum 타입은 같은 타입에 속한 필드는 모두 같은 enum 타입으로 봄. 때문에 아래와 같이 IpAddrKind 타입의 파라미터를 받는 함수에 V4, V6 모두 사용 가능.


enum IpAddrKind {
V4,
V6
}

fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

route(four);
route(six);
}

fn route(ip_type: IpAddrKind) {
// do something..
}


아래와 같이 값을 지정할 수도 있음. 보다시피 타입은 자유롭게 지정 가능. 이렇게 값을 지정할 경우, 값은 필수가 됨.


enum IpAddrKind {
V4(u8, u8, u8, u8),
V6(String)
}

let four = IpAddrKind::V4(127, 0, 0, 1);
let six = IpAddrKind::V6(String::from("::1"));


구조체를 값으로 지정할 수도 있음. 이 방법으로 여러 값과 다양한 형태를 정의할 수 있음.


struct Ipv4Addr {

}

struct Ipv6Addr {

}

enum IpAddrKind {
V4(Ipv4Addr),
V6(Ipv6Addr)
}


아래는 enum 을 정의하는 다양한 방법.


enum Message {
Quit,
Move {x: i32, y:i32},
Write(String),
ChangeColor(i32, i32, i32),
}


  • Quit: 아무 데이터도 없는 형태.
  • Move: 익명 구조체 형태.
  • Write: String 타입의 값을 가지는 형태.
  • ChangeColor: i32 타입의 값 3개를 가지는 형태.

각각 아래와 같이 선언. 각 변수는 모두 Message 타입이 됨.

let message_quit = Message::Quit;
let message_move = Message::Move{x: 30, y: 108};
let message_write = Message::Write(String::from("hello"));
let message_cc = Message::ChangeColor(253, 253, 253);

이런 enum 의 형태는 구조체로 치면 아래와 같음. 

struct QuitMessage;
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String);
struct ChangeColorMessage(i32, i32, i32);

그리고 구조체와 같은 방식으로 메서드를 정의할 수 있음.

impl Message {
fn call(&self) {
// do something..
}
}

let m = Message::Write(String::from("hello"));
m.call();



'Rust' 카테고리의 다른 글

Enum(match)  (0) 2018.02.21
Enum(Option<T>)  (0) 2018.02.21
구조체(메서드)  (0) 2018.02.20
구조체(활용)  (0) 2018.02.20
구조체(기본)  (0) 2018.02.19

메서드


메서드는 함수와 비슷하지만 염연히 구분됨. 쉽게 말해서, 특정 타입에 종속되어서 해당 타입의 런타임 인스턴스와 관련된 기능을 제공하는 것이 메서드이고, 특정 타입에 종속되지 않는 기능을 하는 것이 함수임. 쉬운 예로 유틸성 기능은 보통 특정 타입과 관련되지 않고 범용적으로 사용되기 때문에 함수로 구현됨.



메서드 정의


이전 글의 예제에 메서드를 추가해봄.


#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}

fn main() {
let rect1 = Rectangle{width: 30, height: 50};

println!("The area of the rectangle is {} square pixels.", rect1.area());
}

이제 area 함수는 Rectangle 타입의 메서드가 되었음. 


메서드는 타입 이름 앞에 impl 키워드를 이용하여 정의됨. impl 블록은 편의에 따라 몇 개든 사용 가능. impl 블록마다 다른 메서드를 정의하는 것도 가능함.


메서드 정의 코드는 함수와 유사한데, 메서드의 특징은 첫 번째 파라미터로 &self 를 받음. 이 self 란 이 타입의 인스턴스 자신을 의미함. 내부적으로 메서드를 호출하면서 호출 주체인 인스턴스 자신을 넘기는 구조이기 때문. 기능을 수행하는 데에 필요한 파라미터가 없더라도 이 &self 는 반드시 필요함. 


&self 에 & 가 필요한 이유는 오너십의 규칙이 여기에도 적용되기 때문. & 를 빼고 self 로 정의하면 area 메서드 호출 시 인스턴스 자신의 오너십을 잃어버려, rect1 은 더 이상 사용할 수 없음.


그리고, 메서드가 인스턴스의 필드값을 변경하는 경우 &mut self 로 정의되어야 함.


impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}

fn changeValues(&mut self, width: u32, height: u32){
self.width = width;
self.height = height;
}
}

fn main() {
let mut rect1 = Rectangle{width: 30, height: 50};

rect1.changeValues(300, 500);

println!("The area of the rectangle is {} square pixels.", rect1.area());
}

changeValues 는 필드의 값을 변경함. &mut self 로 정의되었으며, rect1 역시 뮤터블이어야 함.


메서드에 self 가 사용되는 경우도 있는데, 이는 주로 인스턴스를 다른 곳으로 이동시키고 기존 변수를 사용하지 못하게 하는 경우임.



Associated Function 


아래와 같이 impl 블록에 정의되는 함수를 associated function 이라 함. 인스턴스를 생성하지 않고 사용할 수 있으며, 특정 인스턴스와 관련되지 않고 해당 구조체와 관련된 것이기 때문에 메서드가 아니라 함수로 불림. 대표적인 예로 지금까지 사용된 String::from 이 associated function 임. (그 정의와 용례에 따르면, 자바의 스태틱 메서드라 이해하면 되지 않을까 조심스레 생각됨)


Associated function 의 주 사용처 중 하나는, 새로운 인스턴스를 편하게 생성하는 기능을 제공할 때임. 그 예는 아래와 같음.


impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {width: size, height: size}
}
}

fn main() {
let mut rect1 = Rectangle::square(30);

println!("The area of the rectangle is {} square pixels.", rect1.area());
}




'Rust' 카테고리의 다른 글

Enum(Option<T>)  (0) 2018.02.21
Enum  (0) 2018.02.21
구조체(활용)  (0) 2018.02.20
구조체(기본)  (0) 2018.02.19
오너십(슬라이스)  (0) 2018.02.17

구조체의 활용



간단한 코드로 구조체를 어떻게 활용하는지 살펴봄.


먼저, 아래의 area 함수는 넓이와 길이를 넘겨 받아 둘을 곱한 결과를 반환함. 이름 그대로 area.


fn main() {
let width1 = 30;
let height1 = 50;

println!("The area of the rectangle is {} square pixels.", area(width1, height1));
}

fn area(width: u32, height: u32) -> u32 {
width * height
}


튜플을 사용하면 다음과 같아짐.


fn main() {
let rect1 = (30, 50);
println!("The area of the rectangle is {} square pixels.", area(rect1));
}

fn area(dimensions: (u32, u32)) -> u32 {
dimensions.0 * dimensions.1
}


이제 구조체를 사용함.


struct Rectangle {
width: u32,
height: u32,
}

fn main() {
let rect1 = Rectangle{width: 30, height: 50};

println!("The area of the rectangle is {} square pixels.", area(&rect1));
}

fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}



구조체에 유용한 기능 추가하기


구조체에 #[derive(Debug)] 를 추가함으로써 구조체의 정보를 보다 쉽게 출력할 수 있음. println! 문자열에 쓰인 {:#?} 은 구조체의 각 필드를 줄바꿈을 이용하여 나타냄. (# 이 줄바꿈임)


#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

fn main() {
let rect1 = Rectangle{width: 30, height: 50};

println!("rect1 is {:#?}", rect1);
}

fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}


아웃풋은 아래와 같음.



러스트에는 이것 말고도 여러가지 특성이 존재함. 여기선 이쯤 해두고, 나머지는 추후에 알아보도록 함.



'Rust' 카테고리의 다른 글

Enum  (0) 2018.02.21
구조체(메서드)  (0) 2018.02.20
구조체(기본)  (0) 2018.02.19
오너십(슬라이스)  (0) 2018.02.17
오너십(레퍼런스와 대여)  (0) 2018.02.15

구조체의 기본



구조체란 0 개 이상의 필드와 메서드로 구성된, 이름을 가진 하나의 데이터 구조를 의미함. 오늘날 사람들에게 가장 친숙한 언어 중 하나인 자바에 비유하면 구조체란 클래스이고, 필드는 변수, 메서드는 메서드라 말할 수 있음. 필드는 각각 서로 다른 데이터 타입을 가질 수 있음.



구조체의 정의와 사용


구조체는 아래와 같이 정의됨.


struct User {
username : String,
email: String,
sign_in_count: u64,
active: bool,
}


그리고 아래와 같이 사용됨. 보다시피 구조체에 선언된 필드 순서와 생성 시의 필드 순서는 관계없음. 오로지 필드 이름으로 매핑.


let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};

println!("{}", user1.email);


구조체 인스턴스 역시 임뮤터블 or 뮤터블로 생성할 수 있으며, 임뮤터블 인스턴스의 필드를 변경하려 하면 컴파일 에러 발생. 그리고, 구조체 인스턴스가 뮤터블로 생성되면 그 인스턴스의 모든 필드가 뮤터블이고, 반대로 임뮤터블로 생성되면 모든 필드가 임뮤터블임. 특정 필드만 임뮤터블/뮤터블로 지정하는 것은 불가능.


아래와 같이 뮤터블로 생성하여야 필드를 변경할 수 있음.


let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};

user1.email = String::from("someone2@example.com");


구조체를 생성할 때마다 구조체 코드를 작성하기는 번거로움. 아래와 같은 구조체 인스턴스 생성 함수를 선언하고 사용할 수 있음.


fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}


아래와 같이 사용.


let mut user1 = build_user(String::from("someone@example.com"), String::from("someusername123"));


인스턴스 생성 함수의 파라미터와 구조체 필드명이 동일하면 파라미터-필드를 자동으로 매핑해줌. 좀 더 편하게 작성할 수 있음. 아래가 그 예시.


fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}

파라미터 email, usrename 가 구조체의 필드명과 같기 때문에 알아서 구조체의 해당 필드로 데이터를 세팅함.


다른 구조체의 값을 기본값으로 사용할 수도 있음.


let user1 = build_user(String::from("someone@example.com"), String::from("someusername123"));

let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};


더 간단하게 아래의 방법도 가능. email, username 만 지정하고 나머지는 user1 의 값을 그대로 사용. 


let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername"),
..user1
};



튜플 구조체


튜플과 유사한 형태로 구조체를 정의하고 사용할 수도 있음. 이를 튜플 구조체라 함. 그룹화된 데이터 각각의 이름이 중요하지 않을 때, 하지만 그룹으로 묶이면서 하나의 의미를 가진 데이터가 되는 경우 이런 구조체가 유용하게 사용될 수 있음.


아래와 같이 정의.


struct Color(i32, i32, i32, String);
struct Point(i32, i32, i32, String);


아래와 같이 사용. 각 인스턴스의 필드는 튜플과 마찬가지로 .{index} 방식으로 접근할 수 있음.


let black = Color(0, 0, 0, String::from("black"));
let origin = Point(0, 0, 0, String::from("x"));

println!("{}", black.0}
println!("{}", black.1}



Unit-Like 구조체


필드가 전혀 존재하지 않는 튜플도 있음. 이를 Unit-Like 구조체라 하는데, 어떤 타입에 특성을 구현할 때 사용할 수 있음. 여기서는 자세히 다루지 않고, 후에 타입의 특성을 다룰 때 다시 등장.




'Rust' 카테고리의 다른 글

구조체(메서드)  (0) 2018.02.20
구조체(활용)  (0) 2018.02.20
오너십(슬라이스)  (0) 2018.02.17
오너십(레퍼런스와 대여)  (0) 2018.02.15
오너십  (0) 2018.02.15

+ Recent posts