Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

예측 가능성

스마트 포인터는 고유 메서드를 추가하지 않습니다 (C-SMART-PTR)

예를 들어, Box::into_raw 함수가 지금과 같은 방식으로 정의된 이유가 바로 이것입니다.

#![allow(unused)]
fn main() {
impl<T> Box<T> where T: ?Sized {
    fn into_raw(b: Box<T>) -> *mut T { /* ... */ }
}

let boxed_str: Box<str> = /* ... */;
let ptr = Box::into_raw(boxed_str);
}

만약 이것이 고유 메서드로 정의되었다면, 호출 지점에서 호출되는 메서드가 Box<T>의 메서드인지 T의 메서드인지 혼란스러울 것입니다.

#![allow(unused)]
fn main() {
impl<T> Box<T> where T: ?Sized {
    // Do not do this.
    fn into_raw(self) -> *mut T { /* ... */ }
}

let boxed_str: Box<str> = /* ... */;

// This is a method on str accessed through the smart pointer Deref impl.
boxed_str.chars()

// This is a method on Box<str>...?
boxed_str.into_raw()
}

변환은 관련된 타입 중 가장 구체적인 타입에 둡니다 (C-CONV-SPECIFIC)

확신이 서지 않을 때는 from_보다 to_/as_/into_를 선호하십시오. 이쪽이 사용하기에 더 편리하기 때문입니다(그리고 다른 메서드와 연쇄하여 호출할 수도 있습니다).

두 타입 간의 많은 변환에서, 한쪽 타입이 분명히 더 “구체적“입니다. 즉, 다른 타입에는 없는 추가적인 불변 조건이나 해석을 제공합니다. 예를 들어, str은 UTF-8로 인코딩된 바이트 시퀀스이므로 &[u8]보다 더 구체적입니다.

변환은 관련된 타입 중 더 구체적인 타입에 두어야 합니다. 따라서 str은 &[u8] 값으로 변환하거나 그로부터 변환하기 위해 as_bytes 메서드와 from_utf8 생성자를 모두 제공합니다. 이 관례는 직관적일 뿐만 아니라, &[u8]와 같은 구체 타입이 끝없는 변환 메서드로 오염되는 것을 방지합니다.

명확한 리시버가 있는 함수는 메서드입니다 (C-METHOD)

다음 방식이

#![allow(unused)]
fn main() {
impl Foo {
    pub fn frob(&self, w: widget) { /* ... */ }
}
}

다음 방식보다 낫습니다

#![allow(unused)]
fn main() {
pub fn frob(foo: &Foo, w: widget) { /* ... */ }
}

특정 타입과 명확하게 연관된 모든 연산에 해당합니다.

메서드는 함수에 비해 많은 장점이 있습니다.

  • 메서드를 사용하기 위해 임포트하거나 경로를 한정할 필요가 없습니다. 적절한 타입의 값만 있으면 됩니다.
  • 메서드 호출 시 자동 차용(가변 차용 포함)이 수행됩니다.
  • “T 타입의 값으로 무엇을 할 수 있는가“라는 질문에 쉽게 답할 수 있게 해 줍니다(특히 rustdoc을 사용할 때).
  • self 표기법을 제공하는데, 이는 더 간결하며 소유권의 구분을 더 명확하게 전달하는 경우가 많습니다.

함수는 출력 매개변수를 받지 않습니다 (C-NO-OUT)

다음 방식이

#![allow(unused)]
fn main() {
fn foo() -> (Bar, Bar)
}

다음 방식보다 낫습니다

#![allow(unused)]
fn main() {
fn foo(output: &mut Bar) -> Bar
}

여러 개의 Bar 값을 반환하는 경우에 해당합니다.

튜플이나 구조체 같은 복합 반환 타입은 효율적으로 컴파일되며 힙 할당이 필요하지 않습니다. 함수가 여러 값을 반환해야 한다면, 이러한 타입 중 하나를 통해 반환해야 합니다.

주요 예외는 다음과 같습니다. 때로는 함수가 호출자가 이미 소유한 데이터를 수정하기 위한 것일 수 있습니다. 예를 들어 버퍼를 재사용하는 경우입니다.

#![allow(unused)]
fn main() {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>
}

연산자 오버로드는 예상을 벗어나지 않습니다 (C-OVERLOAD)

내장 구문이 있는 연산자(*, | 등)는 std::ops의 트레이트를 구현하여 타입에 제공할 수 있습니다. 이러한 연산자에는 강한 기대가 따릅니다. Mul은 곱셈과 어느 정도 유사하고 (결합 법칙과 같은 기대되는 성질을 공유하는) 연산에 대해서만 구현하십시오. 다른 트레이트도 마찬가지입니다.

스마트 포인터만 Deref와 DerefMut를 구현합니다 (C-DEREF)

Deref 트레이트는 여러 상황에서 컴파일러에 의해 암묵적으로 사용되며, 메서드 해석과 상호작용합니다. 관련 규칙은 스마트 포인터를 수용하도록 특별히 설계되었으므로, 이 트레이트는 그 목적으로만 사용해야 합니다.

표준 라이브러리의 예

생성자는 정적 고유 메서드입니다 (C-CTOR)

Rust에서 “생성자“는 관례에 불과합니다. 생성자 이름 짓기에는 다양한 관례가 있으며, 그 차이는 미묘한 경우가 많습니다.

가장 기본적인 형태의 생성자는 인수가 없는 new 메서드입니다.

#![allow(unused)]
fn main() {
impl<T> Example<T> {
    pub fn new() -> Example<T> { /* ... */ }
}
}

생성자는 자신이 생성하는 타입의 정적(self가 없는) 고유 메서드입니다. 타입 이름을 완전히 임포트하는 관행과 결합하면, 이 관례는 정보를 충분히 담으면서도 간결한 생성 코드로 이어집니다.

#![allow(unused)]
fn main() {
use example::Example;

// Construct a new Example.
let ex = Example::new();
}

일반적으로 타입의 인스턴스를 만드는 주된 메서드에는 new라는 이름을 사용해야 합니다. 위의 예처럼 인수를 받지 않는 경우도 있습니다. Box::new처럼 Box에 넣을 값을 전달받는 등 인수를 받는 경우도 있습니다.

일부 타입의 생성자, 특히 I/O 리소스 타입의 생성자는 File::open, Mmap::open, TcpStream::connect, UdpSocket::bind처럼 생성자에 별도의 명명 규칙을 사용합니다. 이러한 경우 이름은 해당 도메인에 적합하게 선택됩니다.

타입을 생성하는 방법이 여러 가지인 경우가 많습니다. 이러한 경우 Mmap::open_with_offset처럼 보조 생성자에 _with_foo 접미사를 붙이는 것이 일반적입니다. 그러나 타입에 생성 옵션이 많다면, 대신 빌더 패턴(C-BUILDER)을 고려하십시오.

일부 생성자는 “변환 생성자“로, 다른 타입의 기존 값으로부터 새로운 타입을 만드는 메서드입니다. 이러한 생성자는 보통 std::io::Error::from_raw_os_error처럼 from_으로 시작하는 이름을 가집니다. 다만 이와 매우 유사한 From 트레이트(C-CONV-TRAITS)도 참고하십시오. from_ 접두사가 붙은 변환 생성자와 From<T> 구현 사이에는 세 가지 차이점이 있습니다.

  • from_ 생성자는 unsafe일 수 있지만, From 구현은 그럴 수 없습니다. 그 한 예로 Box::from_raw가 있습니다.
  • from_ 생성자는 u64::from_str_radix처럼 원본 데이터의 의미를 명확히 하기 위해 추가 인수를 받을 수 있습니다.
  • From 구현은 원본 데이터 타입만으로 출력 데이터 타입의 인코딩을 결정할 수 있을 때에만 적절합니다. u64::from_be나 String::from_utf8처럼 입력이 단지 비트 덩어리일 때는 변환 생성자의 이름으로 그 의미를 나타낼 수 있습니다.

타입이 Default와 new 생성자를 모두 구현하는 것은 흔하며 권장되는 일입니다. 둘 다 갖춘 타입이라면 두 가지가 같은 동작을 해야 합니다. 어느 한쪽을 다른 쪽을 이용해 구현해도 됩니다.

표준 라이브러리의 예