메서드


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



메서드 정의


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


#[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

슬라이스



fn main() {
let mut s = String::from("hello, world!");

let i = first_world(&s);

s.clear(); // 문자열 제거 (공백 문자열 "" 이 됨)

println!("{}", i);
}

fn first_world(s: &String) -> usize {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}


위 코드에서, 변수 i 는 공백 문자의 인덱스, 6 을 가짐. 그런데 s.clear 가 호출되면서 원 문자열은 사라지고, 변수 i 의 값은 그 의미를 상실함: 인덱스 숫자 6은 여전히 남아있지만, 문자열이 사라졌기 때문에 존재 가치가 사라지는 셈. 관련된 데이터가 서로 독립적으로 존재하기 때문에 이런 현상이 발생함.


이를 방지하기 위해 슬라이스를 사용할 수 있음.


슬라이스는 다음과 같이 [시작위치..끝위치] 를 지정함으로써 생성됨.


let mut s = String::from("hello, world!");

let hello = &s[0..5]; // 인덱스 0 부터 5
let hello = &s[..5]; // 인덱스 0 부터 5

let world = &s[7..12]; // 인덱스 7 부터 12
let world = &s[7..]; // 인덱스 7 부터 12
let hello_world = &s[..len]; // 인덱스 0 부터 끝까지
let hello_world = &s[0..len]; // 인덱스 0 부터 끝까지
let hello_world = &s[..]; // 인덱스 0 부터 끝까지


슬라이스는 아래와 같은 효과를 가짐. (s 가 원본 변수, world 가 슬라이스) 지정된 범위의 시작을 가리키는 포인터를 가지며, 역시 원본 데이터와 같은 데이터를 가리킴.



아까의 함수 first_world 예제로 돌아가서, 슬라이스를 사용하면 world 는 원본 데이터를 가리키기 때문에 아까와 같은 현상은 발생하지 않음. 


fn first_world(s: &String) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}


이제 인티저 인덱스 값이 아닌, 슬라이스를 반환. 슬라이스의 타입은 &str 임. 이는 스트링 리터럴과 같은 타입임. (스트링 리터럴이 &str 인 이유는, 소스 코드에 입력된 값(리터럴)은 그대로 바이러니에 존재하고, 스트링 리터럴은 담고 있는 변수는 이 바이러니의 특정 위치(리터럴이 존재하는 위치)를 참조하는 것이기 때문. 그렇기에 스트링 리터럴은 태생적으로 참조이며, &str 이며, 임뮤터블임.)


여기서 아까처럼 원본 데이터에 변경되면?


fn main() {
let mut s = String::from("hello, world!");

let i = first_world(&s);

s.clear(); // 문자열 제거 (공백 문자열 "" 이 됨)

println!("{}", i);
}

위 코드는 컴파일 에러를 일으킴. 


레퍼런스 대여 규칙에, 한 데이터에 대한 임뮤터블 레퍼런스가 존재하면 이 데이터의 뮤터블 레퍼런스를 대여할 수 없도록 되어 있음. first_world 는 임뷰터블 변수 i 로 슬라이스를 반환하였고, 변수 i 는 원본 데이터를 가리키는(슬라이스는 원본 데이터를 가리키니까) 임뮤터블 변수가 됨. 그런데 문자열을 지워버리는 함수 clear 는 당연히 뮤터블 레퍼런스가 필요함. 여기서 충돌이 발생하여 컴파일 시점에 에러가 발생함.


러스트의 오너십과 슬라이스의 동작은 이런 방식으로 컴파일 타임에 레퍼런스의 혼재에 따른 안정성을 획득함.



'Rust' 카테고리의 다른 글

구조체(활용)  (0) 2018.02.20
구조체(기본)  (0) 2018.02.19
오너십(레퍼런스와 대여)  (0) 2018.02.15
오너십  (0) 2018.02.15
컨트롤 플로우(루프)  (0) 2018.02.15

레퍼런스와 대여



힙 데이터를 가리키는 변수의 오너십을 유지하면서 포인터를 넘기는 방법이 있음.


fn main() {

let s1 = String::from("hello");


let len = calculate_length(&s1);


println!("The length of '{}' is {].", s1, len);

}


fn calculate_length(s: &String) -> usize {

s.len()

}


&s1 에 주목. calculate_length 를 호출하면서 &s1 을 넘겨, String 이 아닌 &String 을 넘겼음.


앰퍼샌드는 레퍼런스를 의미함. 앰퍼샌드는 오너십을 넘기지 않으면서 포인터를 전달할 수 있도록 함.


&String s 는 String s1 를 가리킴.


let s1 = String::from("hello");


let len = calculate_length(&s1);


&s1 은 s1 의 값을 가리키지만, 오너십을 가지지 않음. 때문에 s1 은 여전히 유효한 변수로 남음.


fn calculate_length(s: &String) -> usize {        // s 는 &String 으로 넘겨받은 값을 가리킴.

s.len()

}                                                          // 여기서 s 는 유효 범위를 벗어나지만, s 는 오너십을 가지지 않았기 때문에 

  // 메모리 해제와 같은 일은 일어나지 않음.


이렇게 레퍼런스를 함수 파라미터로 빌리는 일을 '대여(borrowing)' 라 함.


이렇게 대여한 레퍼런스의 값을 바꾸려 한다면?


fn main() {

let s = String::from("hello");


change(&s);

}


fn change(some_string: &String) {

some_string.push_str(", world");

}

위 코드는 컴파일 에러를 일으킴. 변수가 기본적으로 임뮤터블이듯, 레퍼런스도 마찬가지.



뮤터블 레퍼런스


아래 방법으로 레퍼런스의 값 변경 가능.


fn main() {

let mut s = String::from("hellow");


change(&mut s);

}


fn change(some_string: &mut String) {

some_string.push_str(", world");

}


먼저, 변수 s 가 뮤터블이어야 함. 그리고 레퍼런스도 &mut 로 얻고, 함수 파라미터도 &mut 으로 받아야 함. 이렇게 하면 레퍼런스의 값을 변경할 수 있음.


그런데 여기에는 한가지 큰 제약이 있음. 뮤터블 레퍼런스 대여는 동시에 하나를 초과할 수 없음.


let mut s = String::from("hello");


let r1 = &mut s;

let r2 = &mut s; 

위 코드는 컴파일 에러 를 일으킴.


이 제약은 러스트만의 특성. 다른 언어는 보통 이를 허용함. 러스트는 이 제약을 통해 데이터 경합을 컴파일 타임에 방지함.


아래의 방식은 가능함.


let mut s = String::from("hello");


{

let r1 = &mut s;

}                        // 여기서 r1 은 유효 범위를 벗어나 사라짐. 이제 새로운 레퍼런스를 생성해도 문제 없음.


let r2 = &mut s;


반면, 임뮤터블은 이 제약에서 자유로움. 왜냐하면 임뮤터블의 값은 태생적으로 변경될 수 없기에, 데이터 경합이 발생해도 안전하기 때문.


그러나, 뮤터블과 임뮤터블을 동시에 생성하면 역시 컴파일 에러 발생. 임뮤터블 변수를 사용할 때는 값이 변경되지 않을 것이라 여김. 그런데 뮤터블 레퍼런스가 생성되어 값을 변경하게 되면 곤란한 상황이 발생하기 때문에 이를 방지하기 위한 제약.



댕글링 레퍼런스


메모리가 해제되어 포인터가 가리키던 값이 사라졌는데, 포인터만 여전히 남아 그 주소를 가리키는 경우, 이 포인터를 댕글링 포인터라 함. 러스트는 역시 컴파일 타임에 이를 방지함.


fn main() {

let reference_to_nothing = dangle();        // 함수 dangle 이 정상적으로 수행되면 String 레퍼런스를 반환

}


fn dangle() -> &String {            // 이 함수는 String 레퍼런스를 반환

let s = String::from("hello");        // String 생성


&s                                        // String 레퍼런스를 반환.

}                                                // 변수 s 가 유효 범위를 벗어나 사라짐. 가리키던 String 도 메모리 해제됨.   

위 코드는 컴파일 에러를 일으킴.



레퍼런스 규칙 정리


1. 언제든 다음 중 하나의 레퍼런스만 취할 수 있음

  • 오직 하나의 뮤터블 레퍼런스
  • 숫자에 상관없는 임뮤터블 레퍼런스
2. 레퍼런스는 언제나, 반드시 유효해야 함.





















'Rust' 카테고리의 다른 글

구조체(기본)  (0) 2018.02.19
오너십(슬라이스)  (0) 2018.02.17
오너십  (0) 2018.02.15
컨트롤 플로우(루프)  (0) 2018.02.15
컨트롤 플로우(if 조건문)  (0) 2018.02.15

오너십


러스트의 메모리 관리는 컴파일러가 몇 가지 규칙을 이용하여 컴파일 타임 체크를 함으로서 이루어짐. 여기서 중요한 개념이 오너십.



오너십의 규칙

  1. 러스트의 각 값은 각자의 변수를 가지며, 이 변수를 오너라 한다.
  2. 한 번에 하나의 오너만이 존재한다.
  3. 오너가 범위(스코프)를 벗어나면, 그 값은 폐기된다.

변수의 범위

변수는 기본적으로 아래와 같이 중괄호 블록 안에서 유효함.

{                            // 변수 s 는 무효: 아직 선언되지 않음.
let s = "hello";    // 변수 s 는 유효: 여기부터 시작
}                            // 범위의 끝. 변수 s 는 이제 무효함.



스트링 타입의 예


String 타입과 스트링 리터럴은 다름. 프로그램에 하드코딩된 문자열은 스트링 리터럴. 스트링 리터럴은 임뮤터블이며, 코드에 그대로 존재하기 때문에 컴파일 되면 바이너리에 그대로 입력되고, 고정된 값이기에 관리에 어려움이 없음. 


반면 런타임에 사용자의 인풋으로 들어온 문자열은 String 타입. String 타입은 뮤터블이며, 그 크기가 어느 정도인지 컴파일 타임에 알 수 없음. String 타입은 힙에 위치함.


String 타임은 아래의 방법으로 생성할 수 있음.


let s = String::from("hello");

이렇게 하면 문자열 "hello" 는 String 타입으로, 힙에 위치하고, 변수 s 가 그 주소를 가짐. (포인터)


변수 s 가 유효 범위를 벗어나면, String 타입 "hello" 는 제거되어야 함. GC 가 있는 언어는 GC 가 그 역할을 담당하고, GC 가 없는 언어는 프로그래머가 일일이 메모리 해제 코드를 작성해야 함. 이는 피곤한 일이며, 실수로 메모리 해제를 잊게 되면 이는 메모리 낭비로 이어짐. (메모리 릭) 너무 일찍 해제하면 필요한 값이 사라져서 변수가 능력을 상실하게 되됨. 같은 메모리를 두 번 해제하면 프로그램 버그로 이어질 수 있음. 오직 한 번의 메모리 할당(allocate)에 정확히 한 번의 해제(free)가 수행되어야 함. 


러스트의 접근 방식: 변수가 유효 범위를 벗어나면 메모리는 자동으로 반환(해제)됨. 러스트는 이 동작을 위해 drop 이라는 특별한 함수를 호출함. 하지만 한 변수가 범위를 벗어났다고 해서 무조건 그 메모리를 해제하면 곤란함. 프로그램의 런타임에는 다양한 상황이 고려되어야 하기에, 러스트는 각각의 상황에 맞는 대응을 함.



데이터 이동


let x = 5;

let y = x;

이 경우, 5 는 인티저 타입으로, 단순하며 값의 크기가 고정되어 있음. 변수 x 와 y 는 스택에 위치하여 자신의 값 자체를 자신이 가짐


let s1 = String:from("hello");

let s2 = s1;

이 경우, s1 은 String 타입으로, 힙에 위치함. 변수 s1 와 s2 은 값을 가진 것이 아닌, 값의 포인터(주소)를 가짐


len: 데이터의 길이

capacity: 데이터를 위해 할당된 메모리의 용량


변수 s1 에 s2 를 대입함은 값을 복사하는 것이 아닌, 포인터를 복사하는 것. 결과는 아래와 같음.



결국 두 변수가 같은 하나의 값을 공유하는 것. 이런 방식을 얕은 복사(shallow copy)라 함. 만약 값 자체를 복사(deep copy)한다면 아래와 같이 될 것. 러스트는 이렇게 동작하지 않음. (일반적으로 다른 프로그래밍 언어도 마찬가지) 값의 크기에 따라 이는 굉장히 비싼 작업이 될 수 있음.


여튼, 변수 s1 와 s2 는 하나의 값을 공유함. 그런데 두 변수가 모두 스코프를 벗어나면? 여기서 문제가 발생: 같은 메모리에 대해 두 번의 메모리 해제 요청이 발생하며 이는 프로그램의 안정성을 심각하게 떨어트릴 수 있음. 


이 상황에 러스트가 취한 방식은, 변수 s1 을 완전히 무효화하여 s1 이 유효 범위를 벗어나도 메모리를 해제할 필요가 없도록 만드는 것. 


let s1 = String::from("hello");

let s2 = s1;


println!("{}, world!", s1);


위 코드는 아래와 같은 컴파일 에러를 일으킴.


error[E0382]: use of moved value: 's1'

...

= note: move occurs because 's1' has type 'std::string::String', which does not implement the 'Copy' trait


러스트는 얕은 복사를 수행하면서 동시에 기존 변수를 무효화 하여 더 이상 사용 불가능하도록 함. 이러한 특성 때문에, 러스트는 이 동작은 '복사' 가 아닌, '이동(move)' 이라 칭함. 위 상황은 변수 s1 이 s2 으로 '이동' 한 것.


s1 의 포인터, len, capacity 정보를 s2 로 복사한 후, s1 자신은 폐기됨.


폐기된 변수에 메모리 해제 작업은 수행되지 않음. 두 변수가 유효 범위를 벗어나면 변수 s2 에만 메모리 해제 작업이 수행되고, 이렇게 메모리 중복 해제 문제는 해결됨. 



데이터 복제


힙에 있는 데이터를 복사하고 싶을 경우, 함수 clone 을 호출. 


let s1 = String::from("hello");

let s2 = s1.clone();


println!("s1 = {}, s2 = {}", s1, s2);

이제 힙의 데이터가 복제되어, 변수 s1, s2 은 각각 다른 데이터를 가리킴.



스택의 데이터


스택의 데이터는 값 자체가 복사되며, 기존 변수는 폐기되지 않음: 예로, 인티저 타입의 경우 데이터의 사이즈를 컴파일 타임에 알 수 있음. 때문에 값을 복사하는 일이 복잡하거나 많은 시간을 필요로 하지 않음. 그리고 다른 변수에 값을 복사한 뒤 기존 변수를 폐기하거나 할 이유가 없음.


러스트에는 Copy 라는 특별한 표식이 있어, 이 표식을 지닌 타입의 변수는 값 복사 + 폐기되지 않게 됨. (ex: 인티저) 만약 Copy 표식을 Drop 표식이 있거나, 부분적으로 Drop 표식을 가진 타입에 추가하면 러스트는 컴파일 에러를 일으킴.


어떤 타입이 Copy 대상인가? -일반적으로 아래 타입들이 있음

  • 모든 인티저 (ex: u32)
  • 불린 (boolean)
  • 캐릭터 (char)
  • 플로팅 포인트 (ex: f64)
  • 튜플 -안에 Copy 타입의 데이터만 존재할 경우

오너쉽과 함수

함수 파라미터로 값을 넘기는 일도 변수에의 값 대입과 같은 동작이 수행됨. 때문에 지금까지 일반 변수로 다룬 내용이 여기에 그대로 적용됨.

fn main() {
let s = String::from("hello");    // String 타입 변수 s 생성

takes_ownership(s);        // s 의 값이 함수 안으로 '이동'됨. 이제 변수 s 는 무효화되어 더 이상 사용될 수 없음.

let x = 5;                    // 인티저 타입 변수 s 생성

makes_copy(x);            // x 의 값이 함수 안으로 '복사'됨. 변수 x 는 여전히 유효함.
}

fn takes_ownership(some_string: String) {    // 파라미터로 값이 '이동'되어 들어옴.
println!("{}", some_string);
}        // 파라미터 변수 some_string 는 유효 범위를 벗어남. 변수가 가리키던 힙 메모리는 해제됨. (drop 호출)


fn makes_copy(some_integer: i32) {       // 파라미터로 값이 '복사'되어 들어옴.
println!("{}", some_string);
}        // 파라미터 변수 some_integer 는 유효 범위를 벗어남. 아무 일도 일어나지 않음.
만약 변수 s 를 takes_ownership 호출 후에 사용하려 했다면 컴파일 에러가 발생할 것.


반환값과 범위

함수의 값 반환에도 똑같은 일이 발생함.

fn main() {
let s1 = gives_ownership();        // gives_ownership 함수가 반환값을 변수 s1 로 '이동'시킴.


let s2 = String::from("hello");        // String 타입 변수 s1 생성.

let s3 = takes_and_gives_back(s2);        // 변수 s2 가 takes_and_gives_back 으로 '이동'하며 폐기되고, 
// takes_and_gives_back 함수의 반환값이 변수 s3 로 '이동'됨.
}

fn gives_ownership() -> String {                // String 반환값이 있음. 반환값은 '이동'된다는 의미.
let some_string = String::from("hello");

some_string                                    // 생성된 some_string 을 반환 (이동)
}


fn takes_and_gives_back(a_string: String) -> String {

a_string                                         //  파라미터로 넘겨받은 값을 다시 반환 (이동)

}


아래와 같이 동시에 다른 타입의 여러 값도 반환 가능.


fn main() {

let s1 = String:from("hello");


let (s2, len) = calculate_length(s1);

}


fn calculate_length(s: String) -> (String, usize) {

let length = s.len();


(s, length)

}






'Rust' 카테고리의 다른 글

오너십(슬라이스)  (0) 2018.02.17
오너십(레퍼런스와 대여)  (0) 2018.02.15
컨트롤 플로우(루프)  (0) 2018.02.15
컨트롤 플로우(if 조건문)  (0) 2018.02.15
함수의 동작  (0) 2018.02.15

컨트롤 플로우(루프)



loop


조건 체크 없이 일단 돈다. 

아래와 같이 사용.


let mut x = 0;


loop {

println!("again!");

x = x + 1;

if x == 10 {

break;

}

}



while


조건을 가지고 돈다.

아래와 같이 사용. (물론 중간에 break 사용 가능)


let mut x = 0;


while x < 10 {

println!("again!");

x = x + 1;

}



for 


배열 등 정해진 범위를 돌 때 유용.

아래와 같이 사용.


let a = [10, 20, 30, 40, 50];


for element in a.iter() {

println!("the value is: {}", element);

}


추가로, 정해진 범위에서 돌며 카운드다운을 수행하는 아래와 같은 방법도 있음.


for number in (1..10).rev() {

println!("{}", number);

}

위 코드의 결과는 다음과 같다.


9

8

7

6

5

4

3

2

1







'Rust' 카테고리의 다른 글

오너십(레퍼런스와 대여)  (0) 2018.02.15
오너십  (0) 2018.02.15
컨트롤 플로우(if 조건문)  (0) 2018.02.15
함수의 동작  (0) 2018.02.15
데이터 타입  (0) 2018.02.14

컨트롤 플로우(if 조건문)



if 조건문


아래와 같이 사용.


if number < 5 {

println!("The number is less than 5.");

} else if number > 5 {

println!("The number is greater than 5.");

} else {

println!("The number is 5.");

}


if 조건식에는 반드시 불린 타입이 와야 함. (자바스크립트나 c 와는 다르다!)

아래 코드는 if 조건식에 불린 타입이 아닌, 인티저 타입이 왔기에 컴파일 에러 발생. (자바스크립트라면 값 1 은 true 를 의미하여 조건문은 참이 됨)


let number = 3;


if number {

println!("The number is 1");

}


아래와 같이 사용하는 것도 가능.


let condition = true;

let number = if condition {

5

} else {

6

};

중괄호는 익스프레션이기에 값 반환 가능. 위의 if 문은 값을 반환하여 number 변수를 초기화 함. 위 코드는 condition 이 true 이므로 조건문은 참이 되어 number 는 5 가 됨. (조건문 끝에 세미콜론이 있음에도 주목)


if 문을 위와 같이 사용할 경우, if 에서 반환하는 모든 값이 같은 타입이어야 함

아래 코드는 컴파일 에러 발생.


let condition = true;

let number = if condition {

5

} else {

"six"

};

위처럼 경우에 따라 변수의 타입이 인티저 혹은 스트링이 될 수 없음. 반드시 하나의 타입으로 통일되어야 함.





'Rust' 카테고리의 다른 글

오너십  (0) 2018.02.15
컨트롤 플로우(루프)  (0) 2018.02.15
함수의 동작  (0) 2018.02.15
데이터 타입  (0) 2018.02.14
변수  (0) 2018.02.14

함수의 동작



러스트의 코딩 관습 상 함수명은 스네이크 케이스를 따른다: 모두 소문자로 하되, 두 단어 사이는 언더스코어(_) 로 연결.


fn first_function() {

println!("The first function.");

}


함수가 선언된 순서에 관계없이 호출 가능: func1 -> func2 순서로 선언되어 있어도 func1 안에서 func2 호출 가능.



함수 파라미터


아래와 같은 방식으로 파라미터를 지정하며, 파라미터에는 반드시 타입을 지정해주어야 함.


fn my_function(x: i32, y: i32) {

println!("The value of x is: {}", x);

println!("The value of y is: {}", y);

}



함수 바디


스테이트먼트와 익스프레션


스테이트먼트는 어떠한 값도 반환하지 않음.

익스프레션은 값을 반환.


아래와 같이 let 키워드를 사용함은 스테이드먼트.


let y = 6;

위 코드는 변수 y 를 생성하고 값 6 으로 초기화 함.


let x = (let y = 6);

위 코드는 컴파일 에러가 발생함. 'let y = 6' 은 스테이트먼트. x 에 값을 대입하려면 오른쪽에는 값을 반환해야 하는데, let y = 6 은 스테이트먼트이기 때문에 어떠한 값도 반환하지 않음. 오직 익스프레션만이 값을 반환.


let y =6 에서의 6 은 익스프레션.

함수 호출은 익스프레션.

매크로 호출은 익스프레션.

중괄호 {} 로 묶인 새 스코프는 익스프레션.


아래 코드는 정상적으로 컴파일 됨.


let x = 5;


let y = {

let x = 3;

x + 1

};


println!("The value of y is: {}", y);


여기서 아래 코드에 주목.


{

let x = 3;

x + 1

}

위 코드는 익스프레션임. 실행 결과는 4. 

주목할 점: x + 1 에는 세미콜론이 없음. 여기에 세미콜론을 붙이면 러스트는 이를 스테이트먼트로 인식하고, 이 코드는 어떠한 값도 반환하지 않음.

이는 함수의 값 반환에도 그대로 적용됨.



함수의 값 반환


함수의 리턴 타입은 아래와 같이 오른쪽 화살표 옆의 타입으로 지정


fn five() -> i32 {

5

}

함수의 값 반환은 위와 같이 함수 바디의 마지막 값으로 이루어짐.

그런데 이전에 언급한대로, 아래와 같이 쓰면 컴파일 에러 발생.


fn five() -> i32 {

5;

}

세미콜론이 붙으면 5는 익스프레션이 아닌, 스테이트먼트가 됨.


return 키워드를 사용하는 경우는 다름.


fn my_number() -> i32 {

return 7;

5

}

return 키워드는 함수 중간에 함수를 종료하고 값을 반환함. 위의 함수는 정상적으로 컴파일되며 7 을 반환.






'Rust' 카테고리의 다른 글

컨트롤 플로우(루프)  (0) 2018.02.15
컨트롤 플로우(if 조건문)  (0) 2018.02.15
데이터 타입  (0) 2018.02.14
변수  (0) 2018.02.14
환경 잡기  (0) 2018.02.13

데이터 타입



러스트는 정적 타입 언어. 컴파일 시점에 러스트는 모든 변수의 타입을 정확히 알아야 함. 

러스트의 데이터 타입은 크게 두가지로 나뉨: 스칼라(scalar) / 컴파운드(compound)



스칼라 타입


스칼라 타입에는 4가지 기본형이 있음: integers, floating-point numbers, booleans, characters


1) 인티저 타입


길이

 부호 

 부호없는 

 8-bix

 i8 

 u8

 16-bit

 i16 

 u16 

 32-bit

 i32 

 u32 

 64-bit

 i64 

 u64 

 arch

 isize 

 usize 


isize 와 usize 는 구동 머신의 타입에 따라 달라짐; 32비트 -> 32비트 / 64비트 -> 64비트


인티저 리터럴에는 숫자 외에 자리수 구분자(ex: 100_000)를 넣을 수 있음.



2) 플로팅 포인트 타입


32비트 / 64비트로 구분. 기본형은 64비트


let x = 2.0    // f64


let y: f32 = 3.0    // f32



3) 불린 타입


아래와 같이 선언


let t = true;    // 타입 생략


let f: bool = false;    // 명시적 타입 지정



4) 캐릭터 타입


문자를 홀따옴표로 묶으면 캐릭터 타입.


let c = 'z';

캐릭터 타입은 유니코드 스칼라 값을 표현 가능. 이는 아스키보다 그 범위가 넓음: U+000 ~ U+D7FF, U+E000 ~ U+10FFFF

유니코드 컨셉이기 때문에 문자 외에 이모지콘이나 기타 문자가 아닌 것도 표현 가능.



컴파운드 타입


1) 튜플


여러 값을 하나로 묶음. 아래와 같이 선언/생성 가능. 다른 타입의 값도 한 튜플에 묶을 수 있음.


let tup: (i32, f64, u8) = (500, 6.4, 1);    // tup 변수에 각각 다른 타입의 3가지 값으로 구성된 튜플 대입


let tup = (500, 6.4, 1);    // 타입 생략 가능(타입 추론)


let (x, y, z) = tup;    // 변수를 선언하고 각각에 튜플 안의 값을 대입


let five_hundred = tup.0;    // 500


let siz_point_four = tup.1;    // 6.4

변수에 튜플을 대입할 경우 튜플의 데이터 수와 선언된 변수의 수가 일치해야 함. 그렇지 않으면 컴파일 에러 발생.



2) 배열


반드시 같은 타입의 값으로 묶여야 함. 배열의 길이는 한 번 정해지면 변하지 않음.


let a = [1, 2, 3, 4, 5];


let first = a[0];

let second = a[1];














'Rust' 카테고리의 다른 글

컨트롤 플로우(루프)  (0) 2018.02.15
컨트롤 플로우(if 조건문)  (0) 2018.02.15
함수의 동작  (0) 2018.02.15
변수  (0) 2018.02.14
환경 잡기  (0) 2018.02.13

+ Recent posts