본문으로 건너뛰기

"codewars" 태그로 연결된 3개 게시물개의 게시물이 있습니다.

모든 태그 보기

Chap.5 Structs

· 약 35분
brown
FE developer
  • 구조체(struct)는 여러 값을 묶어서 어떤 의미를 갖는 데이터 단위를 정의하는 데에 사용합니다.
  • 객체지향 언어에 익숙하신 분들은 구조체를 "객체가 갖는 데이터 속성"과 같은 개념으로 이해하셔도 좋습니다.
  • 이번 장에선 앞서 배운 튜플과 구조체 간 비교, 구조체 사용법, 구조체의 데이터와 연관된 동작을 표현하는 메소드, 연관함수(associated functions) 정의 방법을 다룹니다.
  • 필요한 데이터 형식을 작성할 때 구조체나 열거형(6장에서 배울 예정입니다)을 이용하면, 여러분이 직접 만든 타입에도 러스트의 컴파일 시점 타입 검사 기능을 최대한 활용할 수 있습니다.

5.1 구조체 정의 및 인스턴트화


  • 구조체는 3장에서 배운 튜플과 비슷합니다.
  • 튜플처럼 구조체의 구성 요소들은 각각 다른 타입이 될 수 있습니다.
  • 그리고 여기에 더해서, 구조체는 각각의 구성 요소에 이름을 붙일 수 있습니다.
  • 따라서 각 요소가 더 명확한 의미를 갖게 되고, 특정 요소에 접근할 때 순서에 의존할 필요도 사라집니다.
  • 결론적으로, 튜플보다 유연하게 사용할 수 있습니다.

구조체를 정의할 땐 struct 키워드와 해당 구조체에 지어줄 이름을 입력하면 됩니다. 이때 구조체 이름은 함께 묶을 데이터의 의미에 맞도록 지어주세요. 이후 중괄호 안에서는 필드(field)라 하는 각 구성 요소의 이름 및 타입을 정의합니다.

struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
  • 정의한 구조체를 사용하려면 해당 구조체 내 각 필드의 값을 정해 인스턴스(instance)를 생성해야 합니다.
  • 구조체 정의는 대충 해당 구조체에 무엇이 들어갈지를 정해둔 양식이며, 인스턴스는 거기에 실제 값을 넣은 것이라고 생각하시면 됩니다. 예시로 확인해 보죠
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}

fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
}
user1.email = String::from("anotheremail@example.com");
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
  • 구조체 내 특정 값은 점(.) 표기법으로 얻어올 수 있습니다. 사용자의 이메일 주소를 얻어야 한다치면 user1.email 처럼 사용할 수 있죠.
  • 변경 가능한 인스턴스라면, 같은 방식으로 특정 필드의 값을 변경할 수도 있습니다.
  • 가변성은 해당 인스턴스 전체가 지니게 됩니다.(일부 필드만 변경 가능하도록 만들 수는 없음)
  • 구조체도 다른 표현식과 마찬가지로 함수 마지막 표현식에서 암묵적으로 새 인스턴스를 생성하고 반환할 수 있습니다.

변수명과 필드명이 같을 때 간단하게 필드 초기화하기

변수명과 구조체 필드명이 같을 땐, 필드 초기화 축약법(field init shorthand)을 사용해서 더 적은 타이핑으로 같은 기능을 구현할 수 있습니다.

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

기존 인스턴스를 이용해 새 인스턴스를 만들 때 구조체 갱신법 사용하기

기존에 있던 인스턴스에서 대부분의 값을 유지한 채로 몇몇 값만 바꿔 새로운 인스턴스를 생성하게 되는 경우가 간혹 있습니다. 그럴 때 유용한 게 바로 구조체 갱신법(struct update syntax)입니다.

struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// 구조체 갱신법 X
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};
// 구조체 갱신법 O
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
}

필드명이 없는, 타입 구분용 튜플 구조체

  • 구조체를 사용해 튜플과 유사한 형태의 튜플 구조체(tuple structs)를 정의할 수도 있습니다.
  • 튜플 구조체는 필드의 이름을 붙이지 않고 필드 타입 만을 정의하며, 구조체 명으로 의미를 갖는 구조체입니다.
  • 이는 튜플 전체에 이름을 지어주거나 특정 튜플을 다른 튜플과 구분 짓고 싶은데, 그렇다고 각 필드명을 일일이 정해 일반적인 구조체를 만드는 것은 배보다 배꼽이 더 큰 격이 될 수 있을 때 유용합니다.

튜플 구조체의 정의는 일반적인 구조체처럼 struct 키워드와 구조체 명으로 시작되나, 그 뒤에는 타입들로 이루어진 튜플이 따라옵니다.

fn main() {
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
  • blackorigin 이 서로 다른 튜플 구조체의 인스턴스이므로, 타입이 서로 달라진다는 점이 중요합니다. 구조체 내 필드 구성이 같더라도 각각의 구조체는 별도의 타입이기 때문이죠.
  • 즉, Color 타입과 Point 타입은 둘 다 i32 값 3 개로 이루어진 타입이지만, Color 타입을 매개변수로 받는 함수에 Point 타입을 인자로 넘겨주는 건 불가능합니다.

필드가 없는 유사 유닛 구조체

  • 필드가 아예 없는 구조체를 정의할 수도 있습니다.
  • 유닛 타입인 () 과 비슷하게 동작하므로 유사 유닛 구조체(unit-like structs) 라 지칭
  • 어떤 타입을 내부 데이터 저장 없이 10장에서 배울 트레잇을 구현하기만 하는 용도로 사용할 때 주로 활용됩니다.

구조체 데이터의 소유권

  • User 구조체 정의에서는 의도적으로 &str 문자열 슬라이스 대신 구조체가 소유권을 갖는 String 타입을 사용했습니다.  - 구조체 인스턴스가 유효한 동안 인스턴스 내의 모든 데이터가 유효하도록 만들기 위해서죠.  - 참조자를 이용해 구조체가 소유권을 갖지 않는 데이터도 저장할 수는 있지만,  - 이는 10장에서 배울 라이프타임(lifetime)을 활용해야 합니다.  - 라이프타임을 사용하면 구조체가 존재하는 동안에 구조체 내 참조자가 가리키는 데이터의 유효함을 보장받을 수 있기 때문이죠.

  • 만약 라이프타임을 명시하지 않고 참조자를 저장하고자 하면 다음처럼 문제가 발생합니다.

  • 위 에러를 해결하여 구조체에 참조자를 저장하는 방법은 10장에서 알아볼 겁니다.

  • 지금 당장은 &str 대신 String 을 사용함으로써 넘어가도록 하죠.


5.2 구조체를 사용한 예제 프로그램


어떨 때 구조체를 사용하면 좋을지를, 사각형 넓이를 계산하는 프로그램을 작성하면서 익혀보도록 합시다.

아래 예제는 함수와 rect의 연관성 X

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
}
  • area 함수의 매개변수는 이제 rectangle 하나뿐입니다.
  • 단, 구조체의 소유권을 가져와 버리면 main 함수에서 area 함수 호출 이후에 rect1 을 더 사용할 수 없으므로rectangle 매개변수의 타입을 불변 참조자 타입으로 정하여 소유권을 빌려오기만 하도록 만들었습니다.
  • 불변 참조자 타입이니 함수 시그니처와 호출시에 & 를 작성합니다.
  • area 함수는 Rectangle 인스턴스의 widthheight 필드에 접근하여 area, 즉 넓이를 계산합니다.
  • 이제 함수 시그니처만 봐도 의미를 정확히 알 수 있네요. widthheight 가 서로 연관된 값이라는 것도 알 수 있고,
  • 0 이나 1 대신 필드명을 사용해 더 명확하게 구분할 수 있습니다.

트레잇 derive 로 유용한 기능 추가하기

  • println! 매크로에는 여러 출력 형식을 사용할 수 있습니다.

  • 그리고 기본 형식인 {} 로 지정할 땐 Display 라는, 최종 사용자를 위한 출력 형식을 사용하죠.

  • 여태 사용했던 기본 타입들은 Display 가 기본적으로 구현돼있었습니다. 1 같은 기본 타입들을 사용자에게 보여줄 수 있는 형식은 딱 한 가지뿐이니까요.

  • 하지만 구조체라면 이야기가 달라집니다. 중간중간 쉼표를 사용해야 할 수도 있고, 중괄호도 출력해야 할 수도 있고, 필드 일부를 생략해야 할 수도 있는 등 여러 경우가 있을 수 있습니다.

  • 러스트는 이런 애매한 상황에 우리가 원하는 걸 임의로 예상해서 제공하려 들지 않기 때문에, 구조체에는 Display 구현체가 기본 제공되지 않습니다.

  • 따라서 println! 에서 처리할 수 없죠.

  • println! 매크로 호출을 println!("rect1 is {:?}", rect1); 으로 바꿔봅시다.

  • {} 내에 :? 를 추가하는 건 println! 에 Debug 라는 출력 형식을 사용하고 싶다고 전달하는 것과 같습니다.

  • 이 Debug 라는 트레잇은 최종 사용자가 아닌, 개발자에게 유용한 방식으로 출력하는, 즉 디버깅할 때 값을 볼 수 있게 해주는 트레잇입니다.

  • 러스트는 디버깅 정보를 출력하는 기능을 자체적으로 가지고 있습니다.

  • 하지만 우리가 만든 구조체에 해당 기능을 적용하려면 명시적인 동의가 필요하므로, 구조체 정의 바로 이전에 #[derive(Debug)] 어노테이션을 작성해주어야 합니다.

    • Annotation은 프로그램 내에서 주석과 유사하게, 프로그래밍 언어에는 영향을 미치지 않으면서 프로그램/프로그래머에게 유의미한 정보를 제공하는 역할을 한다.
    • 컴파일러가 특정 오류를 억제하도록 지시하는 것과 같이 프로그램 코드의 일부가 아닌 프로그램에 관한 데이터를 제공, 코드에 정보를 추가하는 정형화된 방법.
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
}
  • 가장 예쁜 출력 형태라 할 수는 없지만, 인스턴스 내 모든 필드 값을 보여주므로 디버깅하는 동안에는 확실히 유용할 겁니다.
  • 필드가 더 많은 구조체라면 이보다 더 읽기 편한 형태가 필요할 텐데요.
  • 그럴 땐 println! 문자열 내에 {:?} 대신 {:#?} 를 사용하면 됩니다.

러스트에선 이처럼 derive 어노테이션으로 우리가 만든 타입에 유용한 동작을 추가할 수 있는 트레잇을 여럿 제공합니다. 이들 목록 및 각각의 동작은 부록 C에서 확인할 수 있으니 참고해주세요. 또한, 여러분만의 트레잇을 직접 만들고, 이런 트레잇들의 동작을 원하는 대로 커스터마이징 해서 구현하는 방법은 10장에서 배울 예정입니다.

본론으로 돌아옵시다. 우리가 만든 area 함수는 사각형의 면적만을 계산하며, Rectangle 구조체를 제외한 다른 타입으로는 작동하지 않습니다. 그렇다면 아예 Rectangle 구조체와 더 밀접하게 만드는 편이 낫지 않을까요? 다음에는 area 함수를 Rectangle 타입 내에 메소드(method) 형태로 정의하여 코드를 리팩토링하는 방법을 알아보겠습니다.


5.3 메소드 문법


  • 메소드(method) 는 함수와 유사합니다.
  • fn 키워드와 함수명으로 선언하고, 매개변수와 반환 값을 가지며, 다른 어딘가로부터 호출될 때 실행됩니다.
  • 하지만 메소드는 함수와 달리 구조체 문맥상에 정의되고(열거형이나 트레잇 객체 안에 정의되기도 하며, 이는 각각 6장, 17장에서 알아보겠습니다), 첫 번째 매개변수가 항상 self 라는 차이점이 있습니다.
    • self 매개변수는 메소드가 호출된 구조체 인스턴스를 나타냅니다.

메소드 정의

기존의 Rectangle 매개변수를 갖던 area 함수를 Rectangle 구조체에 정의된 area 메소드로 바꿔봅시다.

#[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()
);
}
  1. Rectangle 내에 함수를 정의하기 위해서
  2. impl (구현: implementation) 블록을 만들고
  3. area 함수를 옮겨온 후,
  4. 기존에 있던 첫 번째 매개변수를 (이 경우엔 유일한 매개변수네요) 함수 시그니처 및 본문 내에서 찾아 self 로 변경했습니다.
  5. 그리고 main 함수 내에선 rect1 을 인수로 넘겨 area 함수를 호출하는 대신, 메소드 문법(method syntax) 을 사용해 Rectangle 인스턴스의 area 메소드를 호출했습니다.
  6. 메소드 문법은 차례대로 인스턴스, 점, 메소드명, 괄호 및 인수로 구성됩니다.
  • area 시그니처부터 살펴보도록 하겠습니다.

  • 기존의 rectangle: &Rectangle 이 &self 로 바뀌었네요.

  • Rectangle 을 명시하지 않아도 되는 이유는, 메소드가 impl Rectangle 내에 있다는 점을 이용해 러스트가 self 의 타입을 알아낼 수 있기 때문입니다.

  • 또한 self 앞에 기존 &Rectangle 처럼 & 가 붙은 점을 주목해주세요.

  • 메소드는 지금처럼 self 를 변경 불가능하게 빌릴 수도 있고, 다른 매개변수처럼 변경 가능하도록 빌릴 수도 있고, 아예 소유권을 가져올 수도 있습니다.

  • &self 를 사용해 변경 불가능하게 빌려온 이유는 기존에 &Rectangle 을 사용했던 이유와 같습니다.

  • 우리가 원하는 건 소유권을 가져오는 것도, 데이터를 작성하는 것도 아닌, 데이터를 읽는 것뿐이니까요.

  • 만약 메소드에서 호출된 인스턴스를 변경해야 한다면? &mut self 를 사용하면 됩니다.

  • self 로만 작성하여 인스턴스의 소유권을 가져오도록 만드는 일은 거의 없습니다.

    • 그나마 self 를 다른 무언가로 변형시키고, 그 이후에는 원본 인스턴스 사용을 막고자 할 때나 종종 볼 수 있죠.

함수 대신 메소드를 이용했을 때의 주요 이점은 메소드 시그니처 내에서 self 의 타입을 반복해서 입력하지 않아도 된다는 것뿐만이 아닙니다.

코드를 더 조직적으로 만들 수 있죠. 우리가 라이브러리를 제공하게 된다고 가정해봅시다. 향후 우리가 제공한 라이브러리를 사용할 사람들이 Rectangle 에 관련된 코드를 라이브러리 곳곳에서 찾아내야 하는 것과, impl 블록 내에 모두 모아둔 것 중에 어떤 것이 나을까요? 답은 명확합니다.

-> 연산자는 없나요?

C 나 C++ 언어에서는 메소드 호출에 두 종류의 연산자가 쓰입니다. 어떤 객체의 메소드를 직접 호출할 땐 . 를 사용하고, 어떤 객체의 포인터를 이용해 메소드를 호출하는 중이라서 역참조가 필요할 땐 -> 를 사용하죠. 예를 들어서 object 라는 포인터가 있다면, object->something() 는 (*object).something() 로 나타낼 수 있습니다.

이 -> 연산자와 동일한 기능을 하는 연산자는 러스트에 없습니다. 러스트에는 자동 참조 및 역참조(automatic referencing and dereferencing) 라는 기능이 있고, 메소드 호출에 이 기능이 포함돼있기 때문입니다.

여러분이 object.something() 코드로 메소드를 호출하면, 러스트에서 자동으로 해당 메소드의 시그니처에 맞도록 &&mut* 를 추가합니다. 즉, 다음 두 표현은 서로 같은 표현입니다: p1.distance(&p2); (&p1).distance(&p2);

첫 번째 표현이 더 깔끔하죠? 이런 자동 참조 동작은 메소드의 수신자(self 의 타입을 말합니다)가 명확하기 때문에 가능합니다. 수신자와 메소드명을 알면 해당 메소드가 인스턴스를 읽기만 하는지(&self), 변경하는지(&mut self), 소비하는지(self) 러스트가 알아낼 수 있거든요. 또한 메소드의 수신자를 러스트에서 암묵적으로 빌린다는 점은, 실사용 환경에서 소유권을 인간공학적으로 만드는 중요한 부분입니다.

더 많은 파라미터를 가진 메소드

Rectangle 구조체의 두 번째 메소드를 구현하여 메소드 사용법을 연습해 봅시다.

  • 새로 만들 메소드는 다른 Rectangle 인스턴스를 받아서,
  • self 사각형 면적 내에 두 번째 사각형 Rectangle 인스턴스가 들어갈 수 있다면 true 를 반환하고,
  • 못 들어가면 false 를 반환할 겁니다.
  • 즉, can_hold 메소드를 정의하여 프로그램이 작동하도록 만들겠습니다:
    • 메소드의 정의는 impl Rectangle 블록 내에 위치할 것이고,
    • 메소드명은 can_hold, 매개변수는 Rectangle 을 불변 참조자로 받겠죠.
    • 이때 매개변수 타입은 메소드를 호출하는 코드를 보면 알아낼 수 있습니다.
    • rect1.can_hold(&rect2) 에서 Rectangle 인스턴스 rect2 의 불변 참조자인 &rect2 를 전달했으니까요.
    • rect2 를 읽을 수만 있으면 되기 때문에 변경 가능하게 빌려올 필요도 없으며, rect2 의 소유권을 main 에 남겨두지 않을 이유도 없으니, 논리적으로도 불변 참조자가 가장 적합합니다.
    • 반환값은 Boolean 타입이 될 거고, self 의 너비, 높이가 다른 Rectangle 의 너비, 높이보다 큰지 검사하는 식으로 구현될 겁니다.
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
let rect3 = Rectangle {
width: 60,
height: 45,
};
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

연관 함수

  • impl 블록에는 self 매개변수를 갖지 않는 함수도 정의할 수 있습니다.
  • 이러한 함수는 구조체의 인스턴스로 동작하는 것이 아니기 때문에 메소드는 아니지만,
  • 구조체와 연관돼있기에 연관 함수(associated functions) 라고 부릅니다.
  • 여러분이 이미 사용해본 연관 함수로는 String::from 이 대표적이군요.

연관 함수는 주로 새로운 구조체 인스턴스를 반환해주는 생성자로 활용됩니다. 예시를 들어보죠.

  • Rectangle 로 정사각형을 만들 때 너비, 높이에 같은 값을 두 번 명시하는 대신,
  • 치수 하나를 매개변수로 받고 해당 치수로 너비 높이를 설정하는 연관함수를 만들어,
  • 더 간단하게 정사각형을 만드는 방법을 제공해보겠습니다:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(3);
}

연관 함수를 호출할 땐 let sq=Rectangle::square(3); 처럼 구조체 명에 :: 구문을 붙여서 호출합니다.

연관 함수는 구조체의 네임스페이스 내에 위치하기 때문이죠. :: 구문은 7장에서 알아볼 모듈에 의해 생성되는 네임스페이스에도 사용됩니다.

impl 블록은 여러 개일 수 있습니다

각 구조체는 여러 개의 impl 블록을 가질 수 있습니다. 다음 Listing 5-16은 Listing 5-15에 나온 코드를 변경해 impl 블록을 여러 개로 만든 모습입니다:

...
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
...

위 코드에서는 impl 블록을 여러 개로 나눠야 할 이유가 전혀 없지만, impl 블록을 반드시 하나만 작성해야 할 필요는 없음을 보여드리기 위해 예시로 작성했습니다. 여러 impl 블록을 유용하게 사용하는 모습은 제네릭 타입 및 트레잇 내용을 다루는 10장에서 보실 수 있습니다.

요약

  • 구조체를 사용하면 우리에게 필요한 의미를 갖는 타입을 직접 만들 수 있습니다.
  • 또한, 구조체를 사용함으로써 서로 관련 있는 데이터들을 하나로 묶어 관리할 수 있으며,
  • 각 데이터 조각에 이름을 붙여 코드를 더 명확하게 만들 수 있습니다.
  • 메소드를 이용하면 여러분이 만든 구조체의 인스턴스에 특정한 동작을 지정해 줄 수도 있고,
  • 연관 함수로 인스턴스가 아닌 구조체 네임스페이스를 대상으로 기능을 추가할 수도 있습니다.

하지만, 구조체로만 커스텀 타입을 만들 수 있는 건 아닙니다. 다음엔 열거형을 배워서 여러분이 쓸 수 있는 도구를 하나 더 늘려보도록 합시다.


codewars

0907

Number of People in the Bus

fn number(bus_stops: &[(i32, i32)]) -> i32 {
let mut come = 0;
let mut out = 0;
for peoples in bus_stops {
let (push, pop) = peoples;
come += push;
out += pop;
}
come - out
}
//
fn number(bus_stops:&[(i32,i32)]) -> i32 {
bus_stops.iter().fold(0,|acc,x| acc + x.0 - x.1)
}
fn number(bus_stops:&[(i32,i32)]) -> i32 {
bus_stops
.into_iter()
.map(|n| n.0 - n.1)
.sum()
}

0910

Testing 1-2-3

fn number(lines: &[&str]) -> Vec<String> {
lines
.iter()
.enumerate()
.map(|(i, &item)| {
let mut str = String::from("");
let idx = i + 1;
let s = format!("{}: {}", idx, item);
str.push_str(&s);
str
})
.collect::<Vec<String>>()
}
//
fn number(lines: &[&str]) -> Vec<String> {
lines.iter().enumerate().map(|x| format!("{}: {}", x.0 + 1, x.1)).collect()
}
fn number(lines: &[&str]) -> Vec<String> {
lines.iter().zip(1..).map(|(x, i)| format!("{i}: {x}")).collect()
}
fn number(lines: &[&str]) -> Vec<String> {
lines.iter().enumerate().map(|(n, line)| format!("{}: {line}", n + 1)).collect()
}

Complementary DNA

fn dna_strand(dna: &str) -> String {
let mut ans = String::from("");
dna.chars().for_each(|c| match c {
'A' => ans.push_str("T"),
'T' => ans.push_str("A"),
'G' => ans.push_str("C"),
_ => ans.push_str("G"),
});
ans
}
//
fn dna_strand(dna: &str) -> String {
dna.chars().map(|c|
match c {
'A' => 'T',
'T' => 'A',
'G' => 'C',
'C' => 'G',
_ => c,
})
.collect()
}
use std::collections::HashMap;

fn dna_strand(dna: &str) -> String {
let complements: HashMap<char, char> = [('A', 'T'), ('T', 'A'), ('C', 'G'), ('G', 'C')].iter().cloned().collect();
dna.chars().map(|c| complements.get(&c).unwrap()).collect()
}

Maximum Length Difference

into_iter, iter의 차이 https://sftblw.tistory.com/91

fn mx_dif_lg(a1: Vec<&str>, a2: Vec<&str>) -> i32 {
if a1.len() == 0 || a2.len() == 0 {
-1
} else {
let a_map1 = a1.into_iter().map(|x| x.len() as i32);
let a_map2 = a_map1.clone();
let b_map1 = a2.into_iter().map(|x| x.len() as i32);
let b_map2 = b_map1.clone();
//
let x_max = a_map1.max().unwrap();
let x_min = a_map2.min().unwrap();
let y_max = b_map1.max().unwrap();
let y_min = b_map2.min().unwrap();

let a = (x_max - y_min).abs();
let b = (y_max - x_min).abs();
if a > b {
a
} else {
b
}
}
}
//
use std::cmp::{max, min};
use std::usize::MAX;

pub fn mx_dif_lg(a1: Vec<&str>, a2: Vec<&str>) -> i32 {
if a1.is_empty() || a2.is_empty() {
return -1;
}
let (max1, min1) = a1
.iter()
.map(|&x| x.len())
.fold((0, MAX), |ac, x| (max(ac.0, x), min(ac.1, x)));
let (max2, min2) = a2
.iter()
.map(|&x| x.len())
.fold((0, MAX), |ac, x| (max(ac.0, x), min(ac.1, x)));

max(((max1 - min2) as i32).abs(), ((max2 - min1) as i32).abs())
}
fn mx_dif_lg(a1: Vec<&str>, a2: Vec<&str>) -> i32 {
if a1.len() == 0 || a2.len() == 0 { return -1 }
let a1_min = a1.iter().map(|s| s.len()).min().unwrap() as i32;
let a1_max = a1.iter().map(|s| s.len()).max().unwrap() as i32;
let a2_min = a2.iter().map(|s| s.len()).min().unwrap() as i32;
let a2_max = a2.iter().map(|s| s.len()).max().unwrap() as i32;
(a1_max - a2_min).max(a2_max - a1_min)
}
fn mx_dif_lg(a1: Vec<&str>, a2: Vec<&str>) -> i32 {
// your code
a1.iter().flat_map(|s1| a2.iter().map(|s2| (s1.len() as i32 - s2.len() as i32).abs()).collect::<Vec<_>>() ).max().unwrap_or(-1)
}

Odd or Even?

fn odd_or_even(numbers: Vec<i32>) -> String {
let sum: i32 = numbers.iter().sum();
if sum % 2 == 0 {
"even".to_string()
} else {
"odd".to_string()
}
}
//
fn odd_or_even(numbers: Vec<i32>) -> String {
match numbers.iter().sum::<i32>() % 2 == 0 {
true => "even".to_string(),
false => "odd".to_string()
}
}
fn odd_or_even(numbers: Vec<i32>) -> String {
(if numbers.iter().sum::<i32>() % 2 == 0 {"even"} else {"odd"}).to_string()
}

Check the exam


fn check_exam(arr_a: &[&str], arr_b: &[&str]) -> i64 {
let ans = arr_a
.iter()
.enumerate()
.map(|(idx, val)| {
if arr_b[idx] == "" {
0
} else if &arr_b[idx] == val {
4
} else {
-1
}
})
.sum();
if ans < 0 {
0
} else {
ans
}
}
//
fn check_exam(arr_a: &[&str], arr_b: &[&str]) -> i64 {
arr_a.iter().zip(arr_b.iter()).fold(0, |pts, ans| {
match ans {
(a, b) if a == b => pts + 4,
(_, b) if b == &"" => pts,
_ => pts - 1
}
}).max(0)
}
fn check_exam(arr_a: &[&str], arr_b: &[&str]) -> i64 {
arr_a
.iter()
.zip(arr_b)
.map(|(&a, &b)| match b {
"" => 0,
_ if a == b => 4,
_ => -1,
})
.sum::<i64>()
.max(0)
}

Highest and Lowest

fn high_and_low(numbers: &str) -> String {
let vec = numbers.split(" ").map(|x| x.parse::<i32>().unwrap());
let vec2 = vec.clone();
let min = vec.min().unwrap();
let max = vec2.max().unwrap();
format!("{} {}", max, min)
}
//
fn high_and_low(numbers: &str) -> String {
use std::cmp;
let f = |(max, min), x| (cmp::max(max, x), cmp::min(min, x));

let answer = numbers
.split_whitespace()
.map(|x| x.parse::<i32>().unwrap())
.fold((i32::min_value(), i32::max_value()), f);
format!("{} {}", answer.0, answer.1)
}
extern crate itertools;
use itertools::Itertools;

fn high_and_low(numbers: &str) -> String {
let (min, max): (i32, i32) = numbers
.split_whitespace()
.map(|s| s.parse().unwrap())
.minmax()
.into_option()
.unwrap();
format!("{} {}", max, min)
}
fn high_and_low(numbers: &str) -> String {
let as_ints: Vec<i32> = numbers.split(" ").map(|x| x.parse().unwrap()).collect();
format!("{} {}", as_ints.iter().max().unwrap(), as_ints.iter().min().unwrap())
}

Sum of Minimums!


fn sum_of_minimums(numbers: [[u8; 4]; 4]) -> u8 {
numbers
.map(|arr| arr.into_iter().min().unwrap())
.iter()
.sum()
}
//
fn sum_of_minimums(numbers: [[u8; 4]; 4]) -> u8 {
numbers.iter().map(|row| row.iter().min().unwrap()).sum()
}
fn sum_of_minimums(numbers: [[u8; 4]; 4]) -> u8 {
numbers.iter().filter_map(|v| v.iter().min()).sum()
}
fn sum_of_minimums(numbers: [[u8; 4]; 4]) -> u8 {
numbers.iter().flat_map(|x| x.iter().min()).sum()
}

Chap.3 common-programming-concepts

· 약 32분
brown
FE developer

변수, 타입, 함수, 주석, 제어문에 대해서 배울 것입니다.

3.1 변수와 가변성


  • 변수는 기본적으로 불변입니다.
  • 이것은 러스트가 제공하는 안정성과 쉬운 동시성이라는 이점을 얻을 수 있는 방향으로 코드를 쓰게 하는 강제사항(nudge)중 하나입니다.
  • mut를 사용해 가변으로 만들 수 있음
    • let mut x = 5;
  • 버그를 방지하는 것 외에도 고려해야 할 비용이 있습니다.(함수형 프로그래밍의 불변성 컨셉에 대한 내용)
    • 예를 들어, 큰 데이터 구조를 사용할 때, 인스턴스를 알맞게 가변으로 설정하는 것은 새로 인스턴스를 할당하고 복사해서 돌려주는 것보다 빠를 수 있습니다.
    • 작은 데이터 구조라면, 새 인스턴스를 만들고 더 함수형 프로그래밍 스타일 로 작성하는 것이 더 흐름을 따라가기 쉽기 때문에, 퍼포먼스가 느려지더라도 명확성을 얻는 것에 대한 패널티로 받아들이는 것이 좋을 수 있습니다.

변수와 상수의 차이

  • 먼저, mut와 상수를 함께 사용할 수 없음 - 상수는 항상 불변
  • 상수는  const 키워드로 선언하며, 값의 타입은 반드시 어노테이션이 달려야 합니다.
  • 마지막 차이점은, 상수는 반드시 상수 표현식이어야 하고 함수의 결과값이나 런타임에 결정되는 그 어떤 값이어도 안된다는 것입니다.
  • 상수를 위한 러스트의 작명 관례는 대문자 스네이크 표기법
    • const MAX_POINTS: u32 = 100_000;

덮어쓰기

  • 새 변수를 이전 변 수명과 같은 이름으로 선언할 수 있고,
  • 새 변수는 이전의 변수를 덮어씁니다.
  • 러스트인들은 첫 번째 변수가 두 번째 변수에 의해 덮어쓰였다라고 표현
    • let x = 5;
    • let x = x + 1;
    • let x = x * 2; -> x is: 12
  • 덮어쓰기는 변수를 mut로 표시하는 것과는 다릅니다.
    • let 키워드 없이 값을 재할당 하려고 한다면 컴파일-타임 에러가 발생하기 때문입니다.
  • mut과 덮어쓰기의 또다른 차이점은, 같은 변수명으로 다른 타입의 값을 저장할 수 있다는 것입니다.

3.2 데이터 타입


러스트에서 사용되는 모든 값들은 어떤 타입을 갖습니다.

그러니 어떤 형태의 데이터인지 명시하여 러스트 컴파일러가 데이터를 어떤 식으로 다룰 수 있는지 알게끔 해야합니다. 여기서는 타입을 스칼라 타입과 복합 타입, 두 가지 부분 집합으로 나누어 보겠습니다.

러스트는 타입이 고정된 (statically typed) 언어라는 점을 주지하세요.

이게 의미하는 바는 모든 변수의 타입이 컴파일 시점에 반드시 정해져 있어야 한다는 겁니다.

보통 컴파일러는 우리가 값을 어떻게 사용하는지에 따라 타입을 추측할 수 있습니다.

하지만 타입의 선택 폭이 넓은 경우는 다음과 같이 반드시 타입을 명시해야 합니다 let guess: u32 = "42".parse().expect("Not a number!");

스칼라 타입

  • 스칼라 타입은 하나의 값을 표현합니다.(JS의 primitive)
    • integers,
    • floating-point numbers
    • Booleans
    • characters.

정수형

  • 정수형_은 소수점이 없는 숫자입니다.
  • 부호 있는 타입은  i로 시작
  • 부호 없는 타입은  u로 시작
LengthSignedUnsigned
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize
  • 부호 있는 (signed), 부호 없는(unsigned)으로 나뉨
    • 오직 양수만을 가질 것인지
    • 부호와 함께 다뤄야 하는 경우 숫자는 더하기 혹은 빼기 기호와 함께 표시하죠.
    • 하지만 숫자가 양수라고 가정해도 문제 없는 상황에는 부호 없이 표시하게 됩니다.
  • 각 부호 있는 타입의 변수는 -(2n - 1) 부터 2n - 1 - 1 까지의 값을 포괄합니다.
  • 여기서  n_은 사용되는 타입의 비트 수 입니다.
    • 따라서 i8은 -(27) 에서 27 - 1 까지의 값, 즉 -128 에서 127 사이의 값을 저장할 수 있습니다.
    • 부호 없는 타입은 0 에서 2n - 1 까지의 값을 저장할 수 있습니다.
    • 그래서 u8 타입은 0 에서 28 - 1 다시 말해, 0 에서 255 까지의 값을 저장할 수 있습니다.
  • 추가로, isize와 usize 타입은 여러분의 프로그램이 동작하는 컴퓨터 환경에 따라 결정됩니다.
    • 64-bit 아키텍처이면 64bit를, 32-bit 아키텍처이면 32bit를 갖게 됩니다.
  • 정수형 리터럴은 Table 3-2에서 보시는 것과 같은 형태로 작성할 수 있습니다.
  • byte 리터럴을 제외한 모든 정수형 리터럴에는 57u8과 같은 타입 접미사와 1_000과 같이 시각적인 구분을 위한 _을 사용할 수 있습니다.
Number literalsExample
Decimal98_222
Hex0xff
Octal0o77
Binary0b1111_0000
Byte (u8 only)b'A'

그러면 어떤 타입의 정수를 사용해야 하는지는 어떻게 알까요? 확실하게 정해진 경우가 아니면 러스트의 기본 값인 i32가 일반적으로는 좋은 선택입니다.

이 타입이 일반적으로 가장 빠르기 때문이죠. 심지어 64-bit 시스템에서도요. isize나 usize는 주로 컬렉션 타입 종류의 인덱스에 사용됩니다.

정수 오버플로우
  • 여러분이 0과 255 사이의 값을 담을 수 있는 u8 타입의 변수를 갖고 있다고 해봅시다.

  • 만약에 이 변수에 256처럼 범위 밖의 값으로 변경하려고 하면 정수 오버플로우 (integer overflow) 가 일어납니다.

  • 코드를 디버그 모드에서 컴파일하는 경우에는 런타임에 정수 오버플로우가 발생했을 때 패닉 (panic) 을 발생시키도록 검사합니다.

    • 러스트에서는 에러가 발생하면서 프로그램이 종료되는 경우 패닉이라는 용어를 사용합니다.
  • --release 플래그를 사용하여 코드를 릴리즈 모드로 컴파일하는 경우에는 패닉을 발생시키는 정수 오버플로우 검사를 실행파일에 포함시키지 않습니다.

    • 대신 오버플로우가 발생하면 러스트는 2의 보수 감싸기 (two's complement wrapping) 을 수행합니다. 짧게 설명하자면, 해당 타입이 가질 수 있는 최대값보다 더 큰 값은 허용되는 최소값으로 “돌아갑니다 (wrap around)”.
    • u8의 경우 256은 0이, 257은 1이 되는 식입니다. 프로그램은 패닉을 발생시키지 않으나, 해당 변수는 아마도 여러분이 예상치 못했던 값을 갖게 될겁니다.
    • 정수 오버플로우의 감싸기 동작에 의존하는 것은 에러로 간주됩니다.

명시적으로 오버플로우의 가능성을 다루기 위해서는 표준 라이브러리가 기본 수치 타입에 대해 제공하는 아래 메소드 종류들을 사용할 수 있습니다:

  • wrapping_add와 같은 wrapping_* 메소드로 감싸기 동작 실행하기
  • checked_* 메소드를 사용하여 오버플로우가 발생하면 None 값 반환하기
  • overflowing_* 메소드를 사용하여 값과 함께 오버플로우 발생이 있었는지를 알려주는 boolean 값 반환하기
  • saturating_* 메소드를 사용하여 값의 최대 혹은 최소값 사이로 제한하기

부동 소수점 타입

러스트의 부동소수점 타입은 f32와 f64로, 각각 32bit와 64bit의 크기를 갖습니다.

기본 타입은 f64인데, 그 이유는 현대의 CPU 상에서 f64가 f32와 대략 비슷한 속도를 내면서도 더 정밀하기 때문입니다.

다음은 부동소수점 숫자의 용례입니다: - let x = 2.0; // f64 - let y: f32 = 3.0; // f32

  • 부동소수점 숫자는 IEEE-754 표준을 따릅니다.
  • f32 타입은 1배수 정밀도 (single-precision)인 부동소수점이고,
  • f64는 2배수 정밀도(double-precision)입니다.

수치 연산

러스트는 모든 숫자 타입에 대해서 여러분이 기대하는 기본 수학 연산 기능을 제공합니다.

부록 B에 Rust가 제공하는 모든 연산자 목록이 있습니다.

Boolean 타입

  • 러스트에서의 boolean 타입도 true와 false 둘 중 하나의 값만 갖습니다.
  • boolean 값은 1 byte 크기입니다.
  • 러스트에서 boolean 타입은 bool로 명시됩니다.

문자 타입

러스트의 char는 이 언어의 가장 기본적인 알파벳 타입입니다.

스트링 리터럴이 큰따옴표를 쓰는 것에 반면, char 타입은 작은따옴표로 쓰는 점을 주목하세요.

  • let c = 'z';

  • let heart_eyed_cat = '😻';

  • 러스트의 char타입은 4 byte 크기이며

  • 유니코드 스칼라 값을 표현하는데, 이는 ASCII 보다 훨씬 더 많은 값을 표현할 수 있다는 의미입니다.

  • 억양 표시가 있는 문자, 한국어/중국어/일본어 문자, 이모지, 넓이가 0인 공백문자 모두가 러스트에서는 유효한 char 값입니다.

  • 유니코드 스칼라 값의 범위는 U+0000에서 U+D7FF, 그리고 U+E000에서 U+10FFFF입니다.

  • 하지만 “문자”는 유니코드를 위한 개념이 아니기 때문에, “문자”에 대한 여러분의 직관은 char와 들어맞지 않을지도 모릅니다. 8장의 “문자열에 UTF-8 텍스트를 저장하기” 에서 이 주제에 대해 자세히 다루겠습니다.

복합 타입

복합 타입 (compound type) 은 여러 값들을 하나의 타입으로 묶을 수 있습니다.

러스트는 튜플(tuple)배열(array) 두 가지 기본 복합 타입을 제공합니다.

튜플 타입

튜플은 다양한 타입의 여러 값들을 묶어 하나의 복합 타입으로 만드는 일반적인 방법입니다.

튜플은 고정된 길이를 갖습니다. 즉, 한번 선언되면 그 크기를 늘리거나 줄일 수 없습니다.

  • 튜플 내의 각 위치는 타입을 갖고, 이 튜플 내의 타입들은 서로 달라도 됩니다. - let tup: (i32, f64, u8) = (500, 6.4, 1);

  • 튜플은 하나의 복합 원소로 취급되므로, 변수 tup은 튜플 전체가 바인딩됩니다.

  • 튜플로부터 개별 값을 얻어오려면 아래와 같이 구조해체 (destructuring)를 하여 튜플 값을 해체하면 사용하면 됩니다

    • `let (x, y, z) = tup
  • 마침표(.) 뒤에 접근하고자 하는 값의 인덱스를 쓰는 방식으로도 값을 얻을 수 있습니다.

    • let six_point_four = tup.1;

배열 타입

여러 값들의 집합체를 만드는 다른 방법으로는 배열이 있습니다.

  • 튜플과는 달리 배열의 모든 요소는 모두 같은 타입이여야 합니다.
  • 러스트의 배열은 튜플과 마찬가지로 고정된 길이를 갖습니다.
  • 여러분이 힙보다는 스택에 데이터를 할당하고 싶을 때나 (힙과 스택은 4장에서 더 다루겠습니다) 항상 고정된 개수의 원소로 이루어진 경우라면 배열이 유용합니다.
  • 하지만 배열은 벡터 타입처럼 유연하지는 않습니다.
    • 벡터는 표준 라이브러리가 제공하는 배열과 유사한 컬렉션 타입인데 크기를 늘리거나 줄일 수 있습니다. 배열을 이용할지 혹은 벡터를 이용할지 잘 모르겠다면, 아마도 벡터를 사용해야 할 겁니다.
    • 8장에서 벡터에 대해 더 자세히 다룰 예정입니다.
  • 예시
    • let a = [1, 2, 3, 4, 5];
    • let a: [i32; 5] = [1, 2, 3, 4, 5]; 배열 원소의 갯수 기입
    • let a = [3; 5]; 3의 값을 가진 원소 5개 [3,3,3,3,3]
배열 요소에 접근하기
  • 배열은 스택에 할당된 단일 메모리 뭉치입니다.
  • 인덱스를 통해 배열 요소에 접근할 수 있습니다
유효하지 않은 배열 요소에 대한 접근
  • 만약 배열의 끝을 넘어선 요소에 접근하려고 하면 컴파일은 되지만 실행 시에 에러가 발생하며 멈추게 됩니다.
  • 컴파일 시점에서는 아무런 에러도 발생하지 않지만, 프로그램은 런타임 (runtime) 에러를 발생시켰고 성공적으로 끝나지 못했습니다.
    • 빌드할 때 error: this operation will panic at runtime 에러와 함께 빌드 안됌
    • could not compile hello-rust due to previous error

여러분이 인덱스를 이용해 원소에 접근 시도를 할 때, 러스트는 여러분이 명시한 인덱스가 배열 길이보다 작은지 검사할 것입니다. 인덱스가 배열 길이보다 크거나 같을 경우 러스트는 패닉(panic)을 일으킵니다.


함수


  • main 함수는 많은 프로그램의 시작 지점입니다.
  • 새로운 함수를 선언하도록 해주는 fn 키워드
  • 러스트 코드는 함수나 변수 이름을 위한 관례로 스네이크 케이스 (snake case) 방식을 이용합니다.
  • 러스트는 여러분의 함수 위치를 고려하지 않으며, 어디든 정의만 되어 있으면 됩니다.

함수 매개변수

  • 함수는 매개변수 (parameter) 를 갖도록 정의될 수 있으며, 이는 함수 시그니처 (function signiture) 의 일부인 특별한 변수입니다.
  • 함수가 매개변수를 갖고 있으면 이 매개변수를 위한 고정값(concrete value)을 전달할 수 있습니다.
  • 전문용어로 이런 고정값을 인자 (argument) 라고 부르지만, 사람들은 보통 매개변수와 인자라는 용어를 함수 정의부 내의 변수나 함수 호출시 집어넣는 고정값에 대해 말할 때 혼용하는 경향이 있습니다.
    • 엄밀히 말해서 parameter는 함수의 정의부분에 나열되어 있는 변수들을 의미하며,
    • argument는 함수를 호출할때 전달되는 실제 값을 의미한다.
    • 이같은 의미를 명확히 하기 위해 parameter는 변수(variable)로, argument는 값(value)으로 보는 것이 일반적이다.

함수 본문은 구문과 표현식으로 구성됩니다

  • 함수 본문은 필요에 따라 표현식(expression)으로 종결되는 구문(statement)의 나열로 구성됩니다.
  • 지금까지는 종결 표현식이 없는 함수만 다뤘지만, 구문의 일부분으로 표현식이 쓰인건 보셨습니다.
  • 러스트는 표현식 기반의 언어이므로, 구문과 표현식의 구분은 러스트 이해에 중요합니다.
  • 다른 언어들은 이런 구분이 없으므로, 구문과 표현식이 무엇이며 둘 간의 차이가 함수의 본문에 어떤 영향을 주는지 살펴보겠습니다.

구문(statement)은 어떤 동작을 수행하고 값을 반환하지 않는 명령입니다. 표현식(expression)은 결과 값을 산출해냅니다.

  • let 키워드로 변수를 만들고 값을 할당하는 것은 구문입니다.
    • 이것이 C나 Ruby 같은 다른 언어와의 차이점인데, 이 언어들은 할당문이 할당된 값을 반환하죠.
    • 이런 언어들에서는 x = y = 6라고 작성하여 x와 y에 모두 6을 대입할 수 있지만,
    • 러스트에서는 이렇지 않습니다.
  • 함수 정의도 구문입니다
  • 5 + 6과 같은 간단한 수학 연산을 살펴봅시다. 이 수식은 11이란 값을 산출하는 표현식입니다.
  • 표현식은 구문의 일부일 수 있습니다.
  • 함수를 호출하는 것도, 매크로를 호출하는 것도 표현식입니다.
  • 아래 예제처럼 새로운 스코프 생성을 위해 사용된 {} 코드 블록도 표현식입니다:
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {}", y); // 4
  • x + 1 줄의 마지막이 세미콜론으로 끝나지 않은 점을 주목하세요.
  • 표현식은 종결을 나타내는 세미콜론을 쓰지 않습니다.
  • 만약 표현식 끝에 세미콜론을 추가하면, 표현식은 구문으로 변경되고 값을 반환하지 않게 됩니다.

반환 값을 갖는 함수

  • 함수는 호출한 코드에게 값을 반환할 수 있습니다.
  • 반환되는 값을 명명해야 할 필요는 없지만, 그 값의 타입은 화살표 (->) 뒤에 선언되어야 합니다.
  • return 키워드와 값을 지정하여 함수로부터 일찍 값을 반환할 수 있지만, 대부분의 함수들은 암묵적으로 마지막 표현식 값을 반환합니다.

3.4 주석


프로그래머들은 주석 (comment) 이라 불리우는 노트를 코드에 남겨서 컴파일러는 이를 무시하지만 코드를 읽는 사람들은 유용한 정보를 얻을 수 있게 합니다.

간단한 주석의 예를 봅시다:

// hello, world

러스트에서 주석은 두개의 슬래시로 시작하며, 이 주석은 해당 줄의 끝까지 계속됩니다.

한 줄을 넘기는 주석의 경우에는 아래처럼 각 줄마다 //를 추가하면 됩니다:

러스트는 문서화 주석 (documentation comment) 라고 불리우는 또다른 주석 형태를 가지고 있는데, 14장의 “크레이트를 Crates.io에 퍼블리싱 하기” 에서 다루도록 하겠습니다.


3.5 흐름 제어문


러스트 코드의 실행 흐름을 제어하도록 해주는 가장 일반적인 재료는 if 표현식과 반복문입니다.

조건식은 반드시 bool 이어야 한다는 점을 주목할 가치가 있습니다.

let 구문에서 if 사용하기

  • if는 표현식이기 때문에 Listing 3-2처럼 let 구문의 우변에 사용할 수 있습니다.
    • let number = if true { 5 } else { 6 };
  • 코드 블록은 블록 안의 마지막 표현식을 계산하고 숫자는 그 자체로 표현식임을 기억하세요

반복문을 이용한 반복

코드 블록을 한 번 이상 수행하는 일은 자주 쓰입니다. 반복 작업을 위해서, 러스트는 몇 가지 반복문(loop) 을 제공합니다.

러스트에는 loopwhile, 그리고 for라는 세 종류의 반복문이 있습니다.

loop로 코드 반복하기

loop 키워드는 여러분이 그만두라고 명시적으로 알려주기 전까지 혹은 영원히 코드 블록을 반복 수행되도록 해줍니다.

반복문에서 값 반환하기

  • loop의 용례 중 하나는 어떤 스레드가 실행 완료되었는지 검사하는 등 실패할지도 모르는 연산을 재시도할 때 입니다.
  • 여기서 해당 연산의 결과를 이후 코드에 넘겨주고 싶을지도 모릅니다.
  • 이를 위해서는 루프 정지를 위해 사용한 break 표현식 뒤에 반환하고자 하는 값을 넣으면 됩니다;
  • 해당 값은 아래와 같이 반복문 밖으로 반환되여 사용 가능하게 됩니다:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}

while을 이용한 조건 반복문

반복문 내에서 조건 검사를 하는 작업도 자주 사용됩니다. 조건문이 참인 동안에는 계속 반복하는 형태죠.

조건문이 참이 아니게 될 때 프로그램은 break를 호출하여 반복을 종료합니다.

이러한 반복문 형태는 loopifelse와 break의 조합으로 구현할 수 있습니다.

하지만 이러한 패턴은 매우 흔하기 때문에 러스트에서는 while 반복문이라 일컫는 구조가 내장되어 있습니다.

	let mut num = 0
while num < 10 {
num += 1
}

for를 이용한 콜렉션에 대한 반복문

for 반복문을 사용하여 콜렉션의 각 아이템에 대한 어떤 코드를 수행시킬 수 있습니다.

	let a = [1,2,3,4,5];
for val in a.iter() {
println("{val}");
}

안전성과 간편성 덕분에 for 반복문은 러스트에서 가장 흔하게 사용되는 반복문 구성요소가 되었습니다.

표준 라이브러리가 제공하는 Range 타입을 이용하면 그렇게 원하는 횟수에 대한 반복문을 구현할 수 있는데, Range는 어떤 숫자에서 시작하여 다른 숫자 종료 전까지의 모든 숫자를 차례로 생성해줍니다.

for number in (1..4)


8 kyu

Grasshopper - Messi Goals

static la_liga_goals: u32 = 43;
static champions_league_goals: u32 = 10;
static copa_del_rey_goals: u32 = 5;

static total_goals: u32 = la_liga_goals+champions_league_goals+copa_del_rey_goals;

Remove First and Last Character

pub fn remove_char(s: &str) -> String {
s[1..s.len() - 1].to_string()
}

Welcome!

fn greet(language: &str) -> &str {
match language {
"czech" => "Vitejte",
"danish" => "Velkomst",
"dutch" => "Welkom",
"estonian" => "Tere tulemast",
"finnish" => "Tervetuloa",
"flemish" => "Welgekomen",
"french" => "Bienvenue",
"german" => "Willkommen",
"irish" => "Failte",
"italian" => "Benvenuto",
"latvian" => "Gaidits",
"lithuanian" => "Laukiamas",
"polish" => "Witamy",
"spanish" => "Bienvenido",
"swedish" => "Valkommen",
"welsh" => "Croeso",
_ => "Welcome",
}
}

Are You Playing Banjo?

  • Rust에는 문자열 타입이 두가지 존재한다. 언어 자체로 지원하는 str과 표준 라이브러리에서 지원하는 String이 그렇다.
  • let s1: &str = "Hello str";
  • let s2: String = String::from("Hello String");
  • str은 보통 &str로 많이 사용한다.
  • String과 &str의 가장 큰 차이점은 String은 문자열 수정이 가능하지만 &str은 불가능하다는 점이다.
  • &str은 보통 문자열 리터럴이나 문자열 슬라이스를 저장하는데 사용된다.
  • 출처: https://steelbear.tistory.com/86 [steelbear's notes:티스토리]
fn are_you_playing_banjo(name: &str) -> String {
let name = name.to_string();
if name.starts_with('r') || name.starts_with('R') {
name + " plays banjo"
} else {
name + " does not play banjo"
}
}
//
fn are_you_playing_banjo(name: &str) -> String {
match &name[0..1] {
"R" | "r" => format!("{} plays banjo", name),
_ => format!("{} does not play banjo", name)
}
}
fn are_you_playing_banjo(name: &str) -> String {
match name.to_lowercase().starts_with("r") {
true => format!("{} plays banjo", name),
false => format!("{} does not play banjo", name)
}
}

7 kyu

The highest profit wins!

fn min_max(lst: &[i32]) -> (i32, i32) {
(*lst.iter().min().unwrap(), *lst.iter().max().unwrap())
}
//
use itertools::Itertools;

fn min_max(xs: &[i32]) -> (i32, i32) {
xs.iter().cloned().minmax().into_option().unwrap()
}
fn min_max(lst: &[i32]) -> (i32, i32) {
let min = lst.iter().min().unwrap();
let max = lst.iter().max().unwrap();

(*min, *max)
}

Regex validate PIN code

Option
Some(_) =>
None =>
fn validate_pin(pin: &str) -> bool {
let len = pin.len();
if len == 4 || len == 6 {
let mut ans = true;
pin.chars().for_each(|x| match x.to_digit(10) {
Some(_) => {}
None => ans = false,
});
ans
} else {
false
}
}
//
fn validate_pin(pin: &str) -> bool {
pin.chars().all(|c| c.is_digit(10)) && (pin.len() == 4 || pin.len() == 6)
}
fn validate_pin(pin: &str) -> bool {
if ![4, 6].contains(&pin.len()) { return false; }
pin.chars().all(|c| c.is_ascii_digit())
}

Printer Errors

fn printer_error(s: &str) -> String {
static ASCII_LOWER: [char; 13] = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
];
let cnt = s
.to_string()
.chars()
.into_iter()
.filter(|x| !ASCII_LOWER.contains(x))
.count()
.to_string();

let ans = cnt + "/" + &s.len().to_string();
ans
}
//
fn printer_error(s: &str) -> String {
// Your cude here
format!("{}/{}", s.chars().filter(|&c| c > 'm').count(), s.len())
}
fn printer_error(s: &str) -> String {
let total = s.len();
let bad = s.chars().filter(|&c| c < 'a' || c > 'm').count();
format!("{}/{}", bad, total)
}

Shortest Word

fn find_short(s: &str) -> u32 {
s.to_string()
.split(" ")
.map(|x| x.len() as u32)
.min()
.unwrap()
}
//
fn find_short(s: &str) -> usize {
s.split_whitespace().map(str::len).min().unwrap()
}
fn find_short(s: &str) -> u32 {
s.split_whitespace()
.map(|word| word.len())
.min()
.unwrap_or(0) as u32
}

Growth of a Population

fn nb_year(p0: i32, percent: f64, aug: i32, p: i32) -> i32 {
let mut cnt = 0;
let mut total = p0 as f64;

while total < p as f64 {
total = total + (total * (percent / 100 as f64)) + aug as f64;
total = total.floor();
cnt += 1
}
cnt
}

Chap.2 gussing-game

· 약 24분
brown
FE developer

해당 학습 자료를 정리 및 수행한 기록입니다.

이번 장은 몇몇 일반적인 Rust 개념과 활용 방법을 배울 수 있습니다.

  • let
  • match
  • 메소드,
  • 연관함수(assiciated functions),
  • 외부 크레이트(external crates)

이번 장에서는 여러분이 직접 기초적인 내용을 실습합니다.

우리는 고전적인 입문자용 프로그래밍 문제인 추리 게임을 구현해 보려 합니다.

  1. 먼저 프로그램은 1~100 사이의 임의의 정수를 생성합니다.
  2. 다음으로 플레이어가 프로그램에 추리한 정수를 입력합니다.
  3. 프로그램은 입력받은 추리값이 정답보다 높거나 낮은지를 알려줍니다.
  4. 추리값이 정답이라면 축하 메세지를 보여주고 종료됩니다.

값을 변수에 저장하기

// 사용자 입력을 받고 결과값을 표시하기 위해서는 io (input/output) 라이브러리를 스코프로 가져와야 합니다.
// io 라이브러리는 std 라고 불리는 표준 라이브러리에 있습니다.

use std::io;

fn main() {
// print
println!("Guess the number!");
println!("Please input your guess.");

// mut string 변수 선언
let mut guess = String::new();

// input 받는 코드
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");

// 출력
println!("You guessed: {}", guess);
}

러스트에서 변수는 기본적으로 불변입니다.

let foo = 5; // 불변
let mut bar = 5; // 가변
  • String::new의 결과값인 새로운 String 인스턴스가 묶이는 대상이 됩니다.

  • String은 표준 라이브러리에서 제공하는 확장 가능한(growable) UTF-8 인코딩의 문자열 타입입니다.

  • ::new에 있는 ::는 new가 String 타입의 연관 함수 (associated function) 임을 나타냅니다.

  • 연관함수는 하나의 타입을 위한 함수이며, 이 경우에는 하나의 String 인스턴스가 아니라 String 타입을 위한 함수입니다.

  • 몇몇 언어에서는 이것을 정적 메소드 (static method) 라고 부릅니다.

  • new 함수는 새로운 빈 String을 생성합니다. new 함수는 새로운 값을 생성하기 위한 일반적인 이름이므로 많은 타입에서 찾아볼 수 있습니다.

요약하자면 let mut guess = String::new(); 라인은 새로운 빈 String 인스턴스와 연결된 가변변수를 생성합니다.

우리는 io의 연관함수인 stdin을 호출합니다:

io::stdin() .read_line(&mut guess)

stdin 함수는 터미널의 표준 입력의 핸들(handle)을 나타내는 타입인 std::io::Stdin의 인스턴스를 돌려줍니다.

코드의 다음 부분인 .read_line(&mut guess)는 사용자로부터 입력을 받기 위해 표준 입력 핸들에서 read_line 메소드를 호출합니다. 또한 read_line에 &mut guess를 인자로 하나 넘깁니다.

  • &는 코드의 여러 부분에서 데이터를 여러 번 메모리로 복사하지 않고 접근하기 위한 방법을 제공하는 참조자 임을 나타냅니다.
  • 참조자는 복잡한 특성으로서 러스트의 큰 이점 중 하나가 참조자를 사용함으로써 얻는 안전성과 용이성입니다.
  • 지금 당장은 참조자가 변수처럼 기본적으로 불변임을 알기만 하면 됩니다. 따라서 가변으로 바꾸기 위해 &guess가 아니라 &mut guess로 작성해야 합니다.

Result 타입으로 잠재된 실패 다루기

  1. read_line은 우리가 인자로 넘긴 문자열에 사용자가 입력을 저장할 뿐 아니라 하나의 값을 돌려 줍니다.
  2. 여기서 돌려준 값은 io::Result 입니다.
  3. 러스트는 표준 라이브러리에 여러 종류의 Result 타입을 가지고 있습니다.
  4. 제네릭 Result이나 io:Result가 그 예시입니다

Result의 variants는 Ok와 Err입니다.

Ok는 처리가 성공했음을 나타내며 내부적으로 성공적으로 생성된 결과를 가지고 있습니다.

Err는 처리가 실패했음을 나타내고 그 이유에 대한 정보를 가지고 있습니다.

io::Result가 Ok 값이라면 expect는 Ok가 가지고 있는 결과값을 돌려주어 사용할 수 있도록 합니다. 이 경우 결과값은 사용자가 표준 입력으로 입력했던 바이트의 개수입니다.

만약 expect를 호출하지 않는다면 컴파일은 되지만 경고가 나타납니다.

println! 변경자(placeholder)를 이용한 값 출력

println!("You guessed: {}", guess);

비밀번호를 생성하기

러스트는 아직 표준 라이브러리에 임의의 값을 생성하는 기능이 없습니다.

하지만 러스트 팀에서는 rand 크레이트를 제공합니다.

크레이트(Crate)를 사용하여 더 많은 기능 가져오기

  • 크레이트는 러스트 코드의 묶음(package)임을 기억하세요.
  • 우리가 만들고 있는 프로젝트는 실행이 가능한 binary crate 입니다.
  • rand crate는 다른 프로그램에서 사용되기 위한 용도인 library crate 입니다.

Cargo에서 외부 크레이트의 활용 예시

  • rand를 사용하는 코드를 작성하기 전에 Cargo.toml 을 수정
    • rand 크레이트를 의존 리스트에 추가
[dependencies]
rand = "0.8.5"

우리는 외부 의존성을 가지게 되었고, Cargo는 Crates.io 데이터의 복사본인 레지스트리(registry) 에서 모든 것들을 가져옵니다.

Crates.io는 러스트 생태계의 개발자들이 다른 사람들도 이용할 수 있도록 러스트 오픈소스를 공개하는 곳입니다.

레지스트리를 업데이트하면 Cargo는 [dependencies] 절을 확인하고 아직 여러분이 가지고 있지 않은 것들을 다운 받습니다.

이 경우 우리는 rand만 의존한다고 명시했지만 rand는 libc에 의존하기 때문에 libc도 다운 받습니다.

러스트는 이것들을 다운받은 후 컴파일하여 의존성이 해결된 프로젝트를 컴파일합니다.

크레이트를 새로운 버전으로 업그레이드하기

Cargo는 update 명령어를 제공합니다.

이것은 Cargo.lock 파일을 무시하고 Cargo.toml 에 여러분이 명시한 요구사항에 맞는 최신 버전을 확인합니다.

확인이 되었다면Cargo는 해당 버전을 Cargo.lock 에 기록합니다.

임의의 숫자를 생성하기

이제 rand 크레이트를 Cargo.toml 에 추가 했으니, rand를 사용 해 봅시다.

정말 놀라운 부분은 버전을 올려서 사용법이 달라졌는데, 사용법과 예시코드까지 보여줌 ㄷㄷ

use rand::Rng;
...
// let secret_number = rand::thread_rng().gen_range(1, 101);
let secret_number = rand::thread_rng().gen_range(1..101);

let mut i = 0;
loop {
if i > 100 {
break;
}
let secret_number = rand::thread_rng().gen_range(1..101);
println!("{} secret number is: {}", i, secret_number);
i += 1;
}
...

  • 먼저 use 라인인 use rand::Rng를 추가합니다.

    • Rng는 난수 생성기를 구현한 메소드들을 정의한 트레잇 (trait) 이며 해당 메소드들을 이용하기 위해서는 반드시 스코프 내에 있어야 합니다. 10장에서 트레잇에 대해 더 자세히 다룰 것입니다.
  • rand::thread_rng 함수는 OS가 시드(seed)를 정하고 현재 스레드에서만 사용되는 특별한 난수 생성기를 돌려줍니다.

  • 다음으로 우리는 gen_range 메소드를 호출합니다.

    • 이 메소드는 Rng 트레잇에 정의되어 있으므로 use rand::Rng 문을 통해 스코프로 가져올 수 있습니다.
    • gen_range 메소드는 두 개의 숫자를 인자로 받고 두 숫자 사이에 있는 임의의 숫자를 생성합니다. 하한선은 포함되지만 상한선은 제외되므로 1부터 100 사이의 숫자를 생성하려면 1과 101을 넘겨야 합니다.

Note: 크레이트에서 트레잇과 메소드, 함수중 어떤 것을 호출해야 할지 모를 수도 있습니다. 각 크레이트의 사용법은 크레이트의 문서에 있습니다.

Cargo의 다른 멋진 기능은 cargo doc --open 명령어를 사용하여 의존하는 크레이트의 문서를 로컬에서 모두 빌드한 다음, 브라우저에서 열 수 있다는 것입니다.

rand 크레이트의 다른 기능이 궁금하시면, cargo doc --open을 실행하고, 왼쪽 사이드바에서 rand를 클릭하여 알 수 있습니다.

비밀번호와 추리값을 비교하기

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
// --snip--

println!("You guessed: {}", guess);

match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}

Listing 2-4: 두 숫자를 비교한 결과 처리하기

  • Ordering은 Result와 같은 열거형이지만 Ordering의 값은 LessGreaterEqual입니다. 이것들은 여러분이 두 개의 값을 비교할 때 나올 수 있는 결과들입니다.
  • cmp 메소드는 두 값을 비교하며 비교 가능한 모든 것들에 대해 호출할 수 있습니다.
    • 이 메소드는 비교하고 싶은 것들의 참조자를 받습니다.
    • 여기서는 guess와 secret_number를 비교하고 있습니다.
    • cmp는 Ordering 열거형을 돌려줍니다.
  • 우리는 match 표현문을 이용하여 cmp가 guess와 secret_number를 비교한 결과인 Ordering의 값에 따라 무엇을 할 것인지 결정할 수 있습니다.

match 표현식은 arm 으로 이루어져 있습니다.

하나의 arm은 하나의 패턴 과 match 표현식에서 주어진 값이 패턴과 맞는다면 실행할 코드로 이루어져 있습니다.

러스트는 match에게 주어진 값을 arm의 패턴에 맞는지 순서대로 확인합니다.

match 생성자와 패턴들은 여러분의 코드가 마주칠 다양한 상황을 표현할 수 있도록 하고 모든 경우의 수를 처리했음을 확신할 수 있도록 도와주는 강력한 특성들입니다.

예제에서 사용된 match 표현식에 무엇이 일어날지 한번 따라가 봅시다.

  1. 사용자가 50을 예측했다고 하고 비밀번호가 38이라 합시다.
  2. 50과 38을 비교하면 cmp 메소드의 결과는 Ordering::Greater 입니다.
  3. match 표현식은 Ordering::Greater를 값으로 받아서 각 arm의 패턴을 확인합니다.
  4. 처음으로 마주하는 arm의 패턴인 Ordering::Less는 Ordering::Greater와 매칭되지 않으므로 첫번째 arm은 무시하고 다음으로 넘어갑니다.
  5. 다음 arm의 패턴인 Ordering::Greater는 확실히 Ordering::Greater와 매칭합니다!
  6. arm과 연관된 코드가 실행될 것이고 Too big!가 출력될 것입니다. 이 경우 마지막 arm은 확인할 필요가 없으므로 match 표현식은 끝납니다.

비교하기 위해서 string을 i32로 변환 해줘야 함

let guess: u32 = guess.trim().parse().expect("Please type a number!");

우리는 guess 변수를 생성했습니다.

잠깐, 이미 프로그램에서 guess라는 이름의 변수가 생성되지 않았나요? 그렇긴 하지만 러스트는 이전에 있던 guess의 값을 가리는(shadow) 것을 허락합니다

. 이 특징은 종종 하나의 값을 현재 타입에서 다른 타입으로 변환하고 싶을 경우에 사용합니다.

Shadowing은 우리들이 guess_str과 guess처럼 고유의 변수명을 만들도록 강요하는 대신 guess를 재사용 가능하도록 합니다. (3장에서 더 자세한 이야기를 다룹니다)

  1. 우리는 guess를 guess.trim().parse() 표현식과 묶습니다.
  2. 표현식 내의 guess는 입력값을 가지고 있던 String을 참조합니다.
  3. String 인스턴스의 trim 메소드는 처음과 끝 부분의 빈칸을 제거합니다.
    • u32는 정수형 글자만을 가져야 하지만 사용자들은 read_line을 끝내기 위해 enter키를 반드시 눌러야 합니다.
    • enter키가 눌리는 순간 개행문자가 문자열에 추가됩니다. 만약 사용자가 5를 누르고 enter키를 누르면 guess는 5\n처럼 됩니다. \n은 enter키, 즉 개행문자를 의미합니다. trim 메소드는 \n을 제거하고 5만 남도록 처리합니다.
  4. 문자열의 parse 메소드는 문자열을 숫자형으로 파싱합니다.
    • 이 메소드는 다양한 종류의 정수형을 변환하므로 우리는 let guess: u32처럼 정확한 타입을 명시해야 합니다.
    • guess 뒤의 콜론(:)은 변수의 타입을 명시했음을 의미합니다.
    • u32은 부호가 없는 32비트의 정수입니다.
    • parse 메소드의 호출은 에러가 발생하기 쉽습니다.
    • 만약 A👍%과 같은 문자열이 포함되어 있다면 정수로 바꿀 방법이 없습니다.
    • Result 타입으로 잠재된 실패 다루기” 에서 read_line와 비슷하게 parse 메소드는 실패할 경우를 위해 Result 타입을 결과로 돌려 줍니다. 우리는 이 Result를 expect 메소드를 사용하여 같은 방식으로 처리합니다.
    • 만약 parse 메소드가 문자열에서 정수로 파싱을 실패하여 Err Result variant를 돌려준다면 expect 호출은 게임을 멈추고 우리가 명시한 메세지를 출력합니다. 만약 parse 메소드가 성공적으로 문자열을 정수로 바꾸었다면 Result의 Ok variant를 돌려 받으므로 expect에서 Ok에서 얻고 싶었던 값을 결과로 받게 됩니다.

반복문을 이용하여 여러 번의 추리 허용

loop 키워드는 무한루프를 제공합니다.

정답 이후에 종료하기

	loop {
println!("Please input your guess.");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}

잘못된 입력값 처리하기

사용자가 숫자가 아닌 값을 입력했을 때 프로그램이 종료되는 동작을 더 다듬어 숫자가 아닌 입력은 무시하여 사용자가 계속 입력할 수 있도록 해 봅시다.

guess가 String에서 u32로 변환되는 라인을 수정하면 됩니다.

let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, };

  • expect 메소드 호출을 match 표현식으로 바꾸는 것은 에러 발생 시 종료되지 않게 처리하는 일반적인 방법입니다.

  • parse 메소드가 Result 타입을 돌려주는 것과 Result는 Ok나 Err variants를 가진 열거형임을 떠올리세요.

  • cmp 메소드의 Ordering 결과를 처리했을 때처럼 여기서 match 표현식을 사용하고 있습니다.

  • 만약 parse가 성공적으로 문자열에서 정수로 변환했다면 결과값을 가진 Ok 를 돌려줍니다.

  • Ok는 첫번째 arm의 패턴과 매칭하게 되고 match 표현식은 parse 가 생성한 num값을 돌려줍니다. 그 값은 우리가 생성하고 있던 새로운 guess과 묶이게 됩니다.

요약

이 프로젝트는 letmatch, 메소드, 연관함수, 외부 크레이트 사용과 같은 많은 새로운 러스트 개념들을 소개하기 위한 실습이었습니다.

Rust_PS in codewars

8 kyu

Convert boolean values to strings 'Yes' or 'No'.

fn bool_to_word(value: bool) -> &'static str {
match value {
true => "Yes",
false => "No",
}
}

DNA to RNA Conversion

fn dna_to_rna(dna: &str) -> String {
let n = dna.len();
let mut ans = String::new();
let mut idx = 0;

loop {
if (idx == n) {
break;
}
let t = dna.chars().nth(idx).unwrap();

match t {
'G' => ans.push_str("G"),
'C' => ans.push_str("C"),
'A' => ans.push_str("A"),
_ => ans.push_str("U"),
}

idx += 1
}
return ans;
}
//
fn dna_to_rna(dna: &str) -> String {
dna.replace("T", "U")
}
fn dna_to_rna(dna: &str) -> String {
dna.chars().map(char_conversion).collect()
}
fn char_conversion(c: char) -> char {
if c == 'T' {
return 'U';
}

c
}
fn dna_to_rna(dna: &str) -> String {
let mut res = String::new();
for s in dna.chars() {
match s {
'T' => res.push('U'),
_ => res.push(s),
}
}
res
}

Counting sheep...

fn count_sheep(sheep: &[bool]) -> u8 {
let mut cnt = 0;
for x in sheep {
if *x {
cnt += 1;
} else {
cnt += 0
}
}
cnt
}
//
fn count_sheep(sheep: &[bool]) -> u8 {
sheep // take the sheep array
.iter() // turn it into an iterable
.filter(|&&x| x) // filter it by taking the values in the array and returning only the true ones
.count() as u8 // count all of the elements in the filtered array and return a u8
}

Fake Binary

fn fake_bin(s: &str) -> String {
let mut ans = String::new();

for x in s.trim().split("").into_iter() {
if x == "" {
continue;
}
let num = x
.parse::<i32>()
.expect("please give me correct string number!");

// println!("{num}");
if num >= 5 {
ans.push_str("1");
} else {
ans.push_str("0");
}
}
println!("{ans}");
ans
}

//
fn fake_bin(s: &str) -> String {
s.chars().map(|c| if c < '5' {'0'} else {'1'}).collect()
}
fn fake_bin(s: &str) -> String {
s.chars()
.map(|c| match c {
'0'..='4' => '0',
'5'..='9' => '1',
_ => c
})
.collect()
}

Switch it Up!

fn switch_it_up(n: usize) -> &'static str {
match n {
0 => "Zero",
1 => "One",
2 => "Two",
3 => "Three",
4 => "Four",
5 => "Five",
6 => "Six",
7 => "Seven",
8 => "Eight",
9 => "Nine",
_ => "",
}
}
//
fn switch_it_up(n: usize) -> &'static str {
match n {
1 => "One",
2 => "Two",
3 => "Three",
4 => "Four",
5 => "Five",
6 => "Six",
7 => "Seven",
8 => "Eight",
9 => "Nine",
_ => "Zero"
}
}
fn switch_it_up(n: usize) -> &'static str {
match n {
0 => "Zero",
1 => "One",
2 => "Two",
3 => "Three",
4 => "Four",
5 => "Five",
6 => "Six",
7 => "Seven",
8 => "Eight",
9 => "Nine",
_ => panic!()
}
}

The Feast of Many Beasts

fn feast(beast: &str, dish: &str) -> bool {
return beast.chars().nth(0) == dish.chars().nth(0)
&& beast.chars().nth(beast.len() - 1) == dish.chars().nth(dish.len() - 1);
}
//
fn feast(beast: &str, dish: &str) -> bool {
beast.chars().next() == dish.chars().next()
&& beast.chars().last() == dish.chars().last()
}
fn feast(beast: &str, dish: &str) -> bool {
dish[..1] == beast[..1] && dish[dish.len()-1..] == beast[beast.len()-1..]
}

Function 2 - squaring an argument

fn square(n: i32) -> i32 {
n * n
}
//
fn square(n: i32) -> i32 {
n.pow(2)
}

Convert number to reversed array of digits

// error
// Creates a temporary which is freed while still in use Again slight_smile
let process = Command::new(location_test);
process.arg(address);

fn digitize(n: u64) -> Vec<u8> {
const RADIX: u32 = 10;
// your code here
let str = n.to_string();
let arr = str
.chars()
.rev()
.map(|x| x.to_digit(RADIX).unwrap())
.collect::<Vec<u32>>();

let mut ans: Vec<u8> = [].to_vec();
arr.into_iter()
.for_each(|val| ans.push(val.try_into().unwrap()));
println!("{ans:#?}");
return ans;
}
// u32 -> u8로 변경하는 부분
fn digitize(n: u64) -> Vec<u8> {
n
.to_string()
.chars()
.map(|c| c.to_digit(10).unwrap() as u8)
.rev()
.collect::<Vec<u8>>()
}