728x90
반응형

Java 개발자를 위한 Rust, 파트 2

우리는 지금까지 어디로 갔습니까?

Java 개발자를 위한 Rust 시리즈의  번째 기사 에서 우리는 몇 가지 기본 구문, 일부 패턴 일치를 보았고 간략하게 다루었으며 가장 중요한 것은 프로그램의 데이터가 있는 위치에 대해 추론하려고 했습니다.enum

우리가 신속하게 코드 라인을 저장하려고 시도했을 때 RAII로 인해 중요한 리소스의 범위를 실수로 변경했고 코드를 컴파일하려고 할 때 빌림 검사기 에서 오류가 발생했습니다.

$ rustc greeter.rs
error[E0716]: temporary value dropped while borrowed
 --> greeter.rs:6:43
  |
6 |     let anybody_at_all = env::args().collect::<Vec<String>>().get(1);
  |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       - temporary value is freed at the end of this statement
  |                          |
  |                          creates a temporary which is freed while still in use
7 |
8 |     let who: &str = if let Some(one) = anybody_at_all {
  |                                        -------------- borrow later used here
  |

두려운 차용 검사기 는 소유borrow later used here 한 값에 대한 참조(차용)를 효과적으로 알려줍니다. 이 경우 소유자가 해제된 동안 임시 가 사용됩니다. 이것은 관리되는 언어에서 오는 Rust를 시도하는 개발자들에게 너무 잘 알려진 오류 메시지입니다. 당신이 직접 Rust를 가지고 놀기 시작했다면 조만간 모험을 하면서 그런 메시지를 접했을 가능성이 있습니다.

Java에는 사용되지 않을 때만 메모리를 해제하는 가비지 수집기가 있을 뿐만 아니라 완전히 투명하게 힙 할당을 최적화할 수 있는 컴파일러도 있습니다. 이렇게 하면 메모리, 해제 또는 메모리 할당 위치(힙 또는 스택)에 대해 생각하지 않아도 됩니다. 반면에 Rust는 개발자에게 메모리 할당 위치와 방법에 대한 결정을 내립니다. 그것은 큰 힘을 동반하지만, 또한… 예, 책임이 있습니다. 적어도 컴파일러는 안전하지 않은 코드를 생성하도록 허용하지 않습니다. 때로는 컴파일러가 작업을 완료하도록 허용하지 않는 것처럼 느껴지기도 합니다. 이것은 일반적으로 프로그램을 통해 데이터가 "흐르는" 방식을 설명하지 않는 코딩 패턴에서 비롯됩니다. 왜냐하면 JVM은 이러한 것들에 대해 생각할 필요가 없고 누구라도 가리킬 수 있는 거대한 객체 바다를 생성하지 않도록 우리를 망쳤기 때문입니다. 모든 것.

소유권 및 이동

cargo new. _ 첫 번째 인수가 있는 경우 전달되도록 코드를 약간 변경할 것입니다. 그리 놀라운 방법은 아닙니다.

use std::env;

fn main() {
    let who = env::args().nth(1).unwrap_or(String::from("World"));
    println!("Hello, {who}!");
}

잠깐만! 이것은 우리가 이전에 본 것과는 다릅니다. 지금까지 우리가 했던 모든 작업이 한 줄짜리인 이유는 무엇입니까? 음… 예. 읽기 쉽게 만들 수 있었습니다 . 또는 적어도 이것이 읽기 쉽기를 바랍니다. 이제 반복자에서 두 번째 .nth(1)항목 을 가져오는 데 필요한 모든 것은 Args여전히 Option<String>​​; 그런 다음 프로그램에 제공된 인수  있는 경우 .unwrap밖으로 이동 합니다 . 반면에 가 있는 경우 대신 메서드에 제공된 인수에서 힙 할당 인스턴스를 만듭니다 .String OptionSome_orNoneString"World": &str

위의 코드에는 다음과 같은 새로운 구문이 있습니다 . 유형 에서 호출 String::from()되는 함수 입니다. 이는 정적 메서드 호출과 동일합니다. 여기 에서 참조하는 는 에서 찾을 수 있습니다 . A 는 Java에서 와 같이 명명된 필드에 관련 데이터를 보유합니다 . 그러나 우리는 그것에 대해 조금 후에 돌아올 것입니다.from()Stringfrom()struct Stringstructclass

그래서 밖으로 옮기는 이 사업  무엇입니까? 이전 게시물에서 보았듯이 는 어딘가에 살아야 합니다 . 그것이 Rust가 소유권이라고 부르는 것입니다. 그리고 모든 데이터의 소유자 는 한 명만 있을 수 있습니다 . 프로그램에 전달된 인수가 있는 경우 에 대한 호출 은 해당 의 소유자인 변형 에서 해당 인수를 반환합니다 . 즉, 범위를 벗어나는 즉시 콘텐츠도 -ped되고 the 가 해제됩니다. 그러나 우리는 우리의 작은 프로그램 전체에서 간접 참조 를 다루고 싶지 않습니다 . 전달 된 인수인지 또는String Option::SomeString env::args().nth(1)StringOption::SomeStringDropStringOptionString"World", 우리는 우리가 인쇄 who할 적절한 것을 가리키는 것 외에는 별로 신경 쓰지 않습니다. String이것이 .unwrap_or()우리를 위해 하는 일입니다.

두 개의 개별 비트를 분해해 보겠습니다. 먼저 Option<String>단순화를 위해 를 다룰 것이므로 먼저 코드를 리팩터링하여 다음을 얻습니다.

let anybody_at_all: Option<String> = env::args().nth(1);
let who: String = anybody_at_all.unwrap_or(String::from("World"));

이제 가 있으므로 프로그램에 항상 인수가 제공 anybody_at_all: Option<String>된다고 가정해 봅시다 . 다음과 같이 메서드 호출을 직선으로 바꿀 수 있습니다 ..unwrap_or.unwrap()

let who: String = anybody_at_all.unwrap();

이 코드는 이제 panic실제로 제공되는 인수가 없을 것입니다. 먼저 인수를 제공한 다음 생략합니다.

$ cargo run -- Jane
   Compiling rusty-java-2 v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.75s
     Running `target/debug/rusty-java-2 Jane`
Hello, Jane!

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/rusty-java-2`
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:5:30
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

좋지 않습니다. 하지만 지금은 무시하고 .unwrap_or곧 우리 버전으로 돌아갈 것입니다. 실제로 성공하는 경로에 집중하여 호출 하면 it이 가진 anybody_at_all.unwrap()소유권을 변수로 이전합니다. 제공된 인수, 즉 이 예에서 from 은 우리의 that 에서 로 이동 했습니다 . 어느 시점에서 당신은 아마 무슨 일이 일어났는지 궁금할 것이고 당신이 묻는 것이 맞을 것입니다. 시행 착오가 여기서 다시 우리의 접근 방식으로 남아 있습니다 . 값 을 다시 시도해 봅시다 .Stringwho: StringString"Jane"Option::Someanybody_at_allwhoanybody_at_all.unwrap()

let anybody_at_all: Option<String> = env::args().nth(1);
let who: String = anybody_at_all.unwrap();
let _again: String = anybody_at_all.unwrap();

무엇이 _again될까요? (밑줄로 변수 이름을 시작하면 컴파일러에 사용할 계획이 없음을 알립니다). 참고가 아닙니다. 또한 에 String포함 Option되어 있으므로 입니다. 그렇다면 원래 문자열의 복사본일까요? 이것을 실행해 봅시다:

$ cargo run -- Jane
   Compiling rusty-java-2 v0.1.0
error[E0382]: use of moved value: `anybody_at_all`
   --> src/main.rs:6:26
    |
4   |     let anybody_at_all: Option<String> = env::args().nth(1);
    |         -------------- move occurs because `anybody_at_all` has type `Option<String>`, which does not implement the `Copy` trait
5   |     let who: String = anybody_at_all.unwrap();
    |                       -------------- -------- `anybody_at_all` moved due to this method call
    |                       |
    |                       help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents
6   |     let _again: String = anybody_at_all.unwrap();
    |                          ^^^^^^^^^^^^^^ value used here after move
    |
note: this function takes ownership of the receiver `self`, which moves `anybody_at_all`
   --> .../lib/rustlib/src/rust/library/core/src/option.rs:775:25
    |
775 |     pub const fn unwrap(self) -> T {
    |                         ^^^^

For more information about this error, try `rustc --explain E0382`.
error: could not compile `rusty-java-2` due to previous error

좋아요, 무서운 오류 메시지입니다: move occurs because …​ does not implement the Copy trait?! 글쎄, 원본의 사본은 String분명히 없습니다. 그 가설에 대해 너무 많이. 그러나 우리는 무언가 움직 인 것을 봅니다 ! anybody_at_all moved 오류 메시지가 나타납니다!  Option자체가 그랬다. 어떤 fn unwrap(self)메서드 때문에 우리는 실제로 호출했습니다. 나중에 구문으로 돌아갑니다. 그러나 무슨 일이 일어났는가는 호출 anybody_at_all이 소비되는 결과를 낳았고 , 그 동안 포함된 it의 소유권 String을 who. 그래서 우리가 그것으로 할 수 있는 것은 아무것도 없고 anybody_at_all, 그것은 "사라지고" 우리가 invoke 를 했던 5행.unwrap() 너머에서 사용될 수 없습니다 .

소유권은 매우 유사한 방식으로 동일한 범위 내에서 한 변수에서 다른 변수로 이전됩니다.

let who: String = anybody_at_all.unwrap();
let moved: String = who;
println!("Hello, {who}!");
println!("Hello, {moved}!");

이것은 우리 가 변수에서 변수로 소유권을 이전하기 때문에 컴파일 되지 않습니다 . 따라서 이후에는 사용할  없으며 더 이상 사용할 수 없습니다.Stringwhomovedwhoprintln!

전화는 어때 .unwrap_or(String::from("World"))? 우리가 변종 panic을 다루는 경우를 제외하고는 정확히 동일하게 작동합니다 . Option::None이 경우 제공된 인수를 대신 반환합니다. 이것은 제네릭 형식 Option<T>이며 unwrap_ora T를 default: T 인수로 사용합니다.  String, 이 경우 a를 전달해야 합니다. 그렇기 때문에 이전 게시물에서 수행한 작업과 &str대신 처리한 문자열과 달리 힙에서 문자열을 인스턴스화해야 합니다. 이 프로그램은 "World" 항상 힙에 문자열을 할당합니다 . 이것은 메서드에 전달되는 인수이며 따라서 평가되어야 합니다. 즉, 함수 String::from가 항상 호출됩니다. 달리는 대안이 있습니다cargo clippy실제로 사용할 것을 권장합니다. 이는 Java의 람다 식에 해당하는 Rust를 사용하여 사용할 기본값을 느리게 평가하고 인수가 이미 제공된 경우 String불필요하게 힙에 할당하는 것을 방지 합니다. "World"그래도 나중 게시물을 위해 보관하겠습니다. 그래도 자유롭게 시도해 보세요!

struct그리고 차용

이제 우리는 소유권과 이사에 대해 상당히 잘 이해했으므로 차입에 대해 좀 더 자세히 살펴볼 때입니다. 앞서 말했듯이 참조는 값을 빌린 것입니다. str로드된 이진 코드에서 a에 대한 참조 또는 String실행 파일에 전달된 인수에서 가져온에 대한 참조를 다루었기 때문에 이전 게시물에서 상당한 양을 차용했습니다 . 이러한 차용은 모두 "읽기 전용"이었습니다. 참조가 가리키는 것을 변경할 수 없다는 의미입니다. &mut그들은 변경 가능한 참조인 참조 에 반대합니다 .

rustc여기 에 우리에게 부과할 중요한 규칙이 있습니다.

주어진 시간에 하나의 가변 참조 또는 여러 불변 참조를 가질 수 있습니다.
— The Rust Book
참조 규칙

즉, "살아 있는" 불변 참조자는 0개 이상 있을 수 있지만, 가변 참조자가 하나(그리고 하나만!) 있는 경우에는 불변 참조자가 0개만 있을 수 있습니다. 이는 값이 변경되는 동안 아무도 값을 읽지 않도록 보장합니다. 이것은 동시성 코드에 유용하지만, 단일 스레드 코드도 이러한 보장으로부터 이점을 얻는다는 것을 조금은 알게 될 것입니다.

그러나 이것이 프로덕션에서 무작위로 발생하는 버그가 아니라 컴파일 타임에 코드에서 전체 범주의 버그를 제거하는 데 어떻게 도움이 되는지 살펴보기 전에 RuntimeException먼저 참조 및 차용이 struct해당 impl블록과 어떻게 작용하는지 살펴보겠습니다. 먼저, structRust에서 a를 선언하는 방법은 다음과 같습니다.

struct Greeter {
    person: String,
}

structRust 의 A class는 Java의 a와 동일합니다. 먼저 struct키워드를 사용하여 선언한 다음 원하는 이름을 지정합니다. 그것이 선언하는 필드는 우리가 지금까지 본 Rust 구문을 따릅니다: <name>: <type>. 위의 예에서 person필드를 선언하는 줄은 쉼표( ,)로 끝납니다. Greeter이것은 구조체 의 마지막(유일한) 필드이므로 꼭 필요한 것은 아닙니다 . 그러나 쉼표가 필요하기 전에 선언되는 필드입니다. 항상 필드 선언을 쉼표로 끝내는 것이 좋은 방법으로 간주되므로 새 항목을 추가해야 하는 경우 버전 제어 시스템의 diff가 더 깔끔해집니다.

현재 우리 는 Java보다 structC에 더 가깝 습니다. 이 단계에서 아마도 생성자와 몇 가지 메서드를 예상할 것입니다. 전자부터 시작하겠습니다. 이 특별한 경우에는 생성자 자체가 필요하지 않습니다. 이 모든 것을 동일한 파일에 넣기 때문에 가시성은 중요하지 않습니다.  명시적으로 . 필드도 마찬가지입니다 . 즉 , 함수 의 어느 지점에서나 다음과 같은 구문을 사용하여 인스턴스화할 수 있습니다.structclassstructpubpersonmainGreeter

let greeter = Greeter {
    person: String::from("Jane"),
};

그리고 Rust의 Java에서 알 수 있듯이 생성자 가 없습니다. struct Greeter예를 들어 일부 인수 조작을 수행 하는 함수를 정의할 수 있습니다. a에 함수를 추가하는 것은 동일한 이름 struct의 블록으로 수행됩니다 . impl다음은 a 를 생성 하고 반환 하는 Greeter::new함수 의 예입니다( 참조가 아닌 자체).Personstruct

impl Greeter {
    fn new(person: String) -> Self {
        Self {
            person,
        }
    }
}

pub fn new다시 말하지만, 이 범위 외부에 노출되어야 하는 경우 함수는 로 public으로 선언 됩니다. 함수가 a 의 소유권 을 갖는 것을 볼 수 있습니다 String. 즉, 호출자가 String소유권을 이 함수로 옮긴다는 의미입니다. →그리고 화살표 a... 로 표시된 함수가 반환됩니다 Self.

Selfimpl블록 유형에 대한 자리 표시자입니다 . 이는 리팩터링할 때나 유형이 다소 장황할 때 유용합니다. 그러나 메서드 서명을 fn new(person: String) → Greeter. 함수 본문인 중괄호 안의 블록을 따릅니다. Greeter그러나 이번에 생성할 때 필드 person가 동일한 이름의 변수를 소유하도록 명시적으로 알리지 않습니다. 이전에 새로 만든 : 과 연결했을 때와 달리 String: person: String::from("Jane"). 이름이 같기 때문에 앞에 를person: 붙일 필요가 없습니다 . 중복되는 것으로 간주되므로 필요하지 않습니다. 마지막으로 본문의 한 줄은 ;새로 생성된Greeter인스턴스를 호출자에게, 즉 소유권을 호출자에게 이동합니다. 이 새로 생성 struct된 것이 스택에 생성되었다는 점도 언급할 가치가 있습니다. person의 이름 바이트가 결국 힙에 있더라도 이것은 String.

다시 Java Greeter::new의 메소드와 동일합니다 . static그렇다면 인스턴스 메서드를 어떻게 추가하시겠습니까? 먼저 인스턴스 메서드가 무엇인지 생각해 보겠습니다. 인스턴스 메소드는 객체 자체의 인스턴스에 작용하는 메소드입니다. 런타임에 발생하는 일은 인스턴스에 대한 참조가 일반 이전 함수에 암시적으로 전달된다는 것입니다. Rust는 이것을 좀 더 명시적으로 만듭니다. 다음은 구조체 에 .hi()메서드를 추가하는 코드입니다 .Greeter

impl Greeter {
    fn new(person: String) -> Self {
        Self {
            person,
        }
    }

    fn hi(&self) {
        let who = self.person.as_str();
        println!("Hello, {who}!");
    }
}

...에 대한 참조 self? 이번에는 소문자 자기? 예. 이것이 Rust가 인스턴스 메서드를 정의하는 방법입니다. 첫 번째 인수는 &self실제로 의 약어 이므로 의 유형에 대한 참조인 바인딩 이름이 지정 self: &Self됩니다 . 첫 번째 인수는 a , a 또는 a 일 수 있습니다 . 우리는 처음에 변형을 보았습니다. 그것은 인스턴스를 소비하는 인스턴스 메서드입니다. 따라서 메서드를 호출하는 데 사용된 바인딩은 일단 호출되면 쓸모 없게 됩니다. 이 구조체와 관련된 상태를 변경하기 때문에 변경 가능한 참조가 필요하다고 컴파일러에 알립니다. 한 가지 예는 Java의 setter이거나 예를 들어 . 제공된 새 항목을 재할당하려면 selfSelfGreeter&self&mut selfselfselfOption.unwrap()&mut selfset_personstruct GreeterString우리 person필드에 대한 참조는 변경 가능해야 합니다.

fn set_person(&mut self, person: String) {
    self.person = person;
}

로 선언 하면 필드 에 새 값을 할당할 때 &self변경하려고 시도하므로 컴파일러에서 불평할 것 입니다.selfperson

$ cargo run -- Jane
   Compiling rusty-java-2 v0.1.0
error[E0594]: cannot assign to `self.person`, which is behind a `&` reference
  --> src/main.rs:32:9
   |
31 |     fn set_person(&self, person: String) {
   |                   ----- help: consider changing this to be a mutable reference: `&mut self`
32 |         self.person = person;
   |         ^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be written

For more information about this error, try `rustc --explain E0594`.
error: could not compile `rusty-java-2` due to previous error

아래는 main.rs이러한 모든 변경 사항이 포함된 현재 상태의 전체 내용입니다. 그리고 몇 가지 작은 변경 사항이 더 있습니다.

   

이제 우리는 메서드 person뿐만 아니라 참조를 반환하는 필드에 대한 getter도 가지고 있습니다 . bye()그 메서드의 본문은 이전에 본 것과 약간 다릅니다 hi(). 둘 다 사실상 동일합니다. 사람의 이름을 인쇄하기 위해 사람의 참조를 얻습니다. 23번째 줄  메서드 hi()에서 self.person필드 를 가져오고 사용할 .as_str()a를 반환하는 the를 호출하는 &str 반면, 28번째 줄println! 에서는 매크로 호출 내에서 the 에 대한 참조 , 즉 a 를 가져 &옵니다 . 두 경우 모두 구조체 밖으로 이동하지 않습니다 . 그 이동은 불법입니다. 우리는 필드만 참조합니다.self.person&Stringself.person

이제 실행하면 다음과 같은 결과가 나타납니다.

❯ cargo run -- Jane
   Compiling rusty-java-2 v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
     Running `target/debug/rusty-java-2 Jane`
Hello, Jane!
... and goodbye, Jane!

이제 이 구조체를 사용하여 차용을 약간 재생하면 이전 의 참조 규칙을 테스트할 수 있습니다.

주어진 시간에 하나의 가변 참조 또는 여러 불변 참조를 가질 수 있습니다.
— The Rust Book
참조 규칙

예를 들어 이것이 무엇을 할 것이라고 생각합니까?

let mut greeter = Greeter::new(String::from("Jane"));

let reference = &greeter;
greeter.set_person(String::from("John"));
reference.hi();

글쎄, 그것은 우리의 간단한 규칙을 위반하기 때문에 컴파일되지 않을 것입니다:

let reference = &greeter;
                -------- immutable borrow occurs here
greeter.set_person(String::from("John"));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
reference.hi();
-------------- immutable borrow later used here

의 메서드 선언을 보면 문제가 표시됩니다 set_person(&mut self, person: Person). 가 필요 &mut self하므로 메서드를 호출할 때 묵시적으로 greeter가변적으로 차용하려고 시도합니다. 불변 참조가 아직 살아 있기 때문에 우리는 할 수 없습니다. greeteron 을 호출하기 위해 대신 사용한다면 .hi()불변 참조는 "죽은" 상태가 되고 코드는 작동할 것입니다…

이 다른 간단한 예가 무엇을 할 것이라고 생각하십니까?

let mut greeter = Greeter::new(String::from("Jane"));

let person = greeter.person();
greeter.set_person(String::from("John"));
greeter.hi();
println!("Our previous person: {person}");

person이것은 우리가 인사 하는 사람 의 필드에 대한 참조를 차용하고 있기 때문에 조금 덜 명확할 수 있습니다 . 그러나 생각해 보면 결과가 이전과 거의 같다는 사실에 놀라지 않을 것입니다.

let person = greeter.person();
             ---------------- immutable borrow occurs here
greeter.set_person(String::from("John"));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
greeter.hi();
println!("Our previous person: {person}");
                                ------ immutable borrow later used here

빌림 자체의 "전이성"은 .person컴파일러 greeter에 의해 추적되므로 동일한 오류가 다시 발생합니다. 내부 에 대한 참조가 아직 있는 동안에는 내부 를 새 것으로 set_person교체 할 수 없습니다 . 이를 교체하면 여전히 사용 중인 이전 값을 -ping하는 것을 의미합니다. 마지막 참조가 만료된 후에만 정리할 가비지 수집기가 없다는 것을 기억하십시오.StringgreeterDrop

이 간단한 예제를 사용하여 참조의 수명 주기 를 실험 하고 참조 규칙이 어떻게 적용되는지 확인할 수 있습니다. Java 개발자로서 이 규칙에 익숙해지고 코드에서 데이터를 처리하는 방법에 대해 더 많이 생각하는 데 시간이 걸립니다. Rust는 데이터와 참조를 저장하는 위치와 방법에 대해 조금 더 생각해야 합니다.

지금 이 규칙이 왜 있는지 궁금하거나 때때로 실제로 도움이 되기보다는 제약이 더 많다고 느꼈을 수도 있습니다. 다른 스레드에 의해 동시에 수정되는 것에 대한 참조 외에 greeter.person, 이 가드에 값을 해제하는 것보다 더 많은 가치가 추가되지 않습니다… 또는 거기에 있습니까? 다음과 같은 간단한 자바 코드를 살펴보겠습니다.

List<Integer> list = new ArrayList<Integer>();

list.add(1);
list.add(2);
list.add(3);

for (Integer i : list) {
    System.out.println(i);
    list.remove(0);
}

목록을 반복하면서 값을 인쇄하고 이동하면서 제거합니다. 이 멍청한 예제와 달리 "실제" 코드에서는 이것이 계속 발생 합니다. 한동안 Java를 사용해 왔다면 이 작업이 이미 명확할 수 있지만 실행 결과는 다음과 같습니다.

1

Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
	at Program.main(Program.java:12)

글쎄, 우리는 이것에 대해 너무 멀리 가지 않았습니다! A ConcurrentModificationException, 단일 스레드에서! 이제 동일한 코드를 Rust에서 사용해 봅시다:

let mut items = vec![1, 2, 3];

for i in &items {
    println!("{i}");
    items.remove(0);
}

여기서 무슨 일이 일어나고 있는지 먼저 설명하겠습니다. Vec<i32>편리한 vec!함수형 매크로에서 다시 생성 합니다. 벡터에 있는 요소의 유형은 i32, 부호 있는 32비트 정수 - Java의 int입니다. 이는 Rust에서 정수 리터럴이 평가하는 것이기 때문입니다.

그런 다음 vec 에 대한 참조 를 반복합니다 ?! 음 ... 아니. 거기에 있는 for 루프에는 컴파일러에 의해 추가된 구문 설탕이 있습니다. for i in items- 앰퍼샌드 없이 - 는 와 동일합니다 for i in items.into_iter(). from 의 소유권을 iterator into_iter() 로 이동 하는 곳 입니다. 결국 각 항목의 소유권은 루프로 이동됩니다. While  루프 의 각 요소에 대한 참조를 제공 하고 컬렉션을 그대로 유지하는 와 동일합니다.Vecveci in &itemsi in items.iter()vecitems

루프 블록은 이제 간단해야 합니다. 이것을 실행하면 어떻게 될까요?

for i in &items {
         ------
         |
         immutable borrow occurs here
         immutable borrow later used here
    println!("{i}");
    items.remove(0);
    ^^^^^^^^^^^^^^^ mutable borrow occurs here

음, 참조 규칙은 우리가 우리의 에 대한 변경 가능한 참조를 가져오는 것을 불가능하게 만들 것입니다. items우리는 암묵적으로 which 가 그것을 변경하기 위해 .removea &mut self를 호출하여 수행 Vec합니다. 우리가 불변 참조를 사용하여 해당 Vec.

이 코드는 어떻습니까?

let mut items = vec![1, 2, 3];

for i in &items {
    for i in &items {
        println!("{i}");
    }
}

이는 참조 규칙을 위반 하지 않습니다 . 키워드가 mut 있지만 참조가 아니라 를 소유하는 변수에 있습니다 Vec<i32>. 그런 다음 중첩 반복으로 이동하여 최대 2개의 불변 참조가 동시에 활성화되며 이는 규칙에 위배 되지 않습니다 . 이 코드는 이러한 루프 후에 itemsthen:을 변경하지 않고 variable does not need to be mutable첫 번째 i가 사용되지 않는 경우 컴파일러에서 경고를 생성하도록 if this is intentional, prefix it with an underscore합니다.

그렇다면 상황은 Drop어떻습니까?

인스턴스가 범위를 벗어나면 컴파일러는 Drop인스턴스를 -ping하는 역할을 하는 일부 코드를 삽입합니다. 즉, 더 많은 인스턴스, 파일 디스크립터, 소켓 등 보유할 수 있는 모든 리소스를 해제하고 결국에는 String 인스턴스용 힙의 메모리와 같이 사용한 메모리를 해제합니다.

Rust가 리소스 해제를 처리하는 방법을 이해하기 위해 약간의 실험을 시작하겠습니다. 로 빌드한 이 hi/bye "관용구"를 기반으로 빌드 Greeter하지만 약간 변경합니다. "안녕하세요!" 한 번은 항상 "bye"라고 표시되어 있는지 확인하십시오. 바로 시작하겠습니다.

   

좋아, 그래서 우리는 이미 무엇을 알고 있습니까? struct두 가지 유형 을 선언 합니다. Scope  TrackedScope. 둘 다 String이라는 힙 할당 필드 를 포함하는 동일한 구조를 갖습니다 name.

필드에는 하나 의 Scope기능과 하나의 메서드가 있습니다. 이 함수는 간단하지만 이전 에 약간의 유용성을 추가하고 Greetera 를 생성하기 전에 &str할당된 힙으로 변환합니다 . 방법 은 조금 더 흥미롭습니다. 참조를 받지 않고 대신 소비 합니다! 그러나 그렇게 하기 전에 범위가 이제 시작되었음을 인쇄한 다음 인스턴스에서 새로 생성된 인스턴스로 이동 한 다음 반환된 인스턴스 를 반환합니다. 따라서 이름은 보존되지만 원래 인스턴스는 삭제됩니다.Stringstruct Scopestart&selfselfTrackedScope StringScopeTrackedScopeScope

에 대해 얘기 Drop! 에 대한 impl블록 도 TrackedScope있지만 특성을 구현합니다 Drop. fn drop(&mut self)Java 인터페이스와 동등한 특성 은 범위가 종료되었음을 인쇄하여 여기에서 구현 하는 단일 메서드를 선언합니다 . 해당 drop메서드 호출은 컴파일러가 올바른 위치에 자동으로 추가하는 것입니다. 코드에서 수동으로 해당 메소드를 호출할 수는 없지만 컴파일러는 (다시!) 이를 금지합니다.

수동으로 drop무언가를 원 .drop()하고 인스턴스에서 호출하는 경우 표준 라이브러리에서 사용할 수 있는 편리한 유틸리티 함수가 있습니다 std::mem::drop. drop그 기능이 얼마나 특별한지 , 신뢰할 수 없는 실제 인스턴스에 어떤 종류의 마법을 사용하는지 궁금하실 것 입니다! 자, 한번 살펴봅시다! 그 아래에는 이 신비한 유틸리티 함수의 전체 소스 코드가 있습니다.

pub fn drop<T>(_x: T) {}

기다리다?! 뭐라고요? 그것은… 비어 있습니다!

예 그렇습니다. 함수 서명에서 마술이 다시 발생합니다. 전달된 인수의 소유권을 갖습니다! 이 메서드는 범위가 없는 일반적인 over T이므로 무엇이든 허용합니다. 그리고 그것 으로 절대 아무것도 하지 마십시오 ! 하지만 그 함수를 호출할 때 의 소유권 _x이 이 범위로 이동한 다음… 컴파일러는 범위를 벗어나는 것을 확인하고 필요한 정리 코드를 삽입합니다!

이제 아래의 몇 가지 경우를 고려하여 코드에서 -ing이 Scope어떻게 발생하는지 지금 사용하겠습니다 .drop

   

주목할 가치가 있는 것은 우리가 10행 에서 끝나는 7행 에 합성 블록을 추가한다는 것 입니다. 또한 범위 는 시작할 때 변수에 바인딩되지 않습니다. 그렇다면 출력은 어떻게 되어야 한다고 생각하십니까? 이것을 실행하고 보자:two

Scope 1 started
Scope 2 started
Scope 2 ended!
Scope 3 started
Scope 3 ended!
Scope 4 started
Scope 4 ended!
Scope 1 ended!

아마 놀랍지 않게, 시작 되기 직전 에 라인 10three 의 블록 끝에서 드롭됩니다 . 반환된 항목을 바인딩하지 않으므로 시작 직후 삭제되지만 바로 정리할 수 있습니다. while 은 해당 블록의 마지막 문과 유사하게만 작동합니다. 외부 범위에서 먼저 then 을 시작하여 결과를 로컬 변수에 바인딩하고 역순으로 -ped되는 것을 볼 수 있습니다 . first , then . 말이 되네요. 나중에 선언된 구조체 바인딩은 이전에 선언된 항목에 대한 참조를 보유할 수 있으므로 역순으로 삭제하면 제대로 정리됩니다!fourtwoTrackedScopethreeonefourdropfourone

요약하자면

그것은 다시 꽤 많은 내용이었습니다. 우리는 지금까지 소유권 의 개념을 훨씬 더 깊이 탐구했습니다. 주어진 데이터 조각의 소유자는 단 한 명뿐입니다. 그러나 해당 소유권은 이전될 수 있으며 이동 이라고도 합니다 . 그런 다음 참조 규칙에 따라 데이터에 대한 참조를 빌릴 수 있는 방법을 살펴보았습니다 . 마지막으로 Rust 컴파일러가 Drop 구조체 인스턴스를 -ping하여 리소스를 해제하는 방법을 확인했습니다.

우리는 실제로 struct우리 자신의 새로운 유형을 선언하고 함수와 메소드를 추가함으로써 이 작업의 대부분을 수행했습니다. &self이렇게 함으로써 우리는 구조체 에 선언될 수 있는 &mut self  가지 다른 유형의 메서드를 살펴보았습니다 self.

마지막으로 Rust에서 for 루프가 작동하는 방식과 Java와 다른 점을 엿볼 수 있습니다. 또한 Rust 표준 라이브러리의 코드를 사용하여 위 원칙의 일부 사용법을 설명했습니다. Java와 마찬가지로 해당 소스 코드를 읽을 수 있으며 Rust 표준 라이브러리 개발자가 언어를 활용하여 몇 가지 기본 문제를 해결하기 위해 코드를 작성하는 방법에 대해 더 잘 알기 위해 일부 시간을 할애하여 일부를 읽는 것이 좋습니다. 우리가 std::mem::drop().

cargo clippy 코드를 자주 실행 하라! 일단 컴파일되면 얻은 것을 리팩토링하는 데 시간을 보내십시오. 그리고 재실행 clippy! 자신의 용어로 언어를 탐색하는 데 시간을 보내는 것이 아마도 Rust 작성을 더 잘 다룰 수 있는 가장 좋은 방법일 것입니다! Java에서는 데이터가 상주해야 하는 위치, 즉 프로그램의 어느 부분이 어떤 데이터를 소유하고 해당 데이터를 효율적으로 공유할 수 있는지에 익숙해지는 것이 처음에는 다소 어려울 수 있습니다. 그러나 데이터가 코드를 통해 흐르는 방식에 대한 저항이 가장 적은 경로를 찾는 것이 결국 더 나은 Java 작성으로 이어질 것이라고 믿습니다.

우리가 다루어야 할 것이 훨씬 더 많습니다. 따라서 Java에서 오는 Rust에 대한 다음 모험과 함께 이번 달에 새로운 게시물을 기대하세요! 이것이 통찰력이 되었기를 바라며, 주저하지 말고 의견, 의견, 제안, 수정 사항, 불만 사항을 남겨주십시오…

https://wcgw.dev/posts/2023/rusty-java-2/#_where_did_we_get_so_far

728x90
728x90
반응형

Java 개발자를 위한 Rust, 소개

시작할 준비를 하세요...

Rust에 대해 들어보셨고 Java 개발자이신가요? Rust를 사용해 보고 싶나요? 이 글은 당신이 기초부터 시작할 수 있도록 노력할 것입니다. Java와 Rust의 기본적인 차이점과 몇 가지 함정에 빠지는 것을 피하고 빌림 검사기와 너무 많이 싸우는 것을 피하려고 합니다. 그 의도는 당신이 Java에서 온 Rust 개발자의 사고방식에 들어가는 데 도움이 되기를 바라는 것입니다.

툴체인 등을 설치하는 방법은 여기서 다루지 않을 것입니다. 그 부분은 충분히 다루었습니다. 그들의 웹사이트에 있는 가이드 는 어쨌든 당신이 필요로 하는 전부일 것입니다.

그럼 본격적으로 Java 프로그램이 Rust 프로그램과 어떻게 다른지 살펴보겠습니다. 보다 더 좋은 예가 Hello, World!있습니까? 이 소개에서 이 예제를 기반으로 시작하여 몇 가지 토대를 마련할 몇 가지 주제를 다룰 것입니다.

첫 번째 프로그램 실행

Java라고 하면 몇 가지를 의미합니다. 물론 언어 자체; JDK 클래스; JDK 도구; 그리고 자바 가상 머신. Hello, World!컴퓨터 화면 에 인쇄하는 데 필요한 모든 것입니다 . HelloWorld.java프로그램 소스 는 다음과 같습니다 .

class HelloWorld {
    public static void main(String... args) {
        System.out.println("Hello, World!");
    }
}

우리가 선언하는 곳에서 class, named HelloWorld. 자체적으로 단일 정적 메서드를 선언하며, 많은 C 스타일 언어와 마찬가지로 일반적으로 main. String마지막으로 해당 메서드는 호출 시 런타임에 의해 채워질 프로그램에 전달된 인수를 나타내는 인스턴스 배열을 허용합니다 .

메서드의 본문은 모든 Java 개발자에게 매우 간단합니다 out. 클래스의 정적 필드를 조회하고 해당 객체 System에서 메서드 .println()를 호출하여 string 을 전달합니다 Hello, World!.

이것을 실행해 봅시다. 여기서는 Java 19를 사용하고 있으므로 java JVM(Java Virtual Machine)에 먼저 컴파일하지 않고 소스 파일을 전달하기만 하면 됩니다.

$ java --version
openjdk 19.0.1 2022-10-18
OpenJDK Runtime Environment (build 19.0.1+10-21)
OpenJDK 64-Bit Server VM (build 19.0.1+10-21, mixed mode, sharing)

$ java HelloWorld.java
Hello, World!

모든 것이 매우 간단하며 실제로 마법이 없습니다. 아니면… 있나요? Rust에서 같은 결과를 얻는 것이 어떻게 약간 다른지 봅시다. 먼저 Rust에서 동등한 프로그램을 작성해야 합니다. 다음은 우리의 작은 것입니다 hello.rs.

fn main() {
    println!("Hello, World!"); 
}

main소스는 상당히 비슷하지만 우리 메서드 에 외부 클래스나 다른 장식이 없다는 것을 알아차렸을 것입니다 . 이 경우에는 함수 이므로 방법이 아닙니다 . 정적 메서드는 함수 자체에 가깝다고 주장할 수 있습니다. main여기서는 확실히 Java의 정적 메서드와 같이 클래스 인스턴스나 클래스에 연결되지 않은 함수 입니다. Rust의 함수는 Java에서와 같이 " funfn " 으로 발음되는 키워드 접두사가 붙은 이름을 사용하여 선언됩니다 . Rust의 함수가 유형이 지정된 인수 목록을 선언할 것으로 예상할 수 있지만 Java에서와 마찬가지로 프로그램의 진입점 역할을 하는 함수는 인수를 취하지 않습니다. 잠시 후에 다시 다루겠습니다.main

마지막으로 Java에서와 마찬가지로 중괄호 안에 있는 함수의 본문은 크게 다르지 않습니다. 함수와 같은 매크로를 호출합니다 . Rust에서 이러한 매크로는 !쉽게 인식할 수 있도록 접미사 가 붙습니다. 어떻게 해결되는지 궁금하실텐데요. 명시적으로 를 참조한 Java와 달리 System.out.println()이 매크로의 출처를 나타내는 항목이 표시되지 않습니다. 매크로의 정의는 에서 찾을 수 있습니다 . 예를 들어 를 찾을 수 있지만 Java 프로그램에서 명시적으로 가져올 필요가 없는 와 어느 정도 동일 std::println!하다고 생각 하면 컴파일러가 이러한 문제를 해결하고 둘 다 그렇게 하는 방법 과 방법에 따라 다르지만 해당 주제는 다른 시간에 유지하겠습니다. 말하자면stdjava.langStringjavacrustcrustc하지만 Rust 컴파일러로 예제를 컴파일해 봅시다. 여기서는 도구 모음 버전 1.66을 사용합니다. 마지막으로 작은 프로그램을 실행합니다.

$ rustc --version
rustc 1.66.0 (69f9c33d7 2022-12-12)

$ rustc hello.rs

$ ./hello
Hello, World!

성공! Java를 사용할 때와 달리 프로그램을 실행하는 데 가상 머신이 필요하지 않기 때문에 명시적인 컴파일 단계를 거쳐야 했습니다. Java 예제를 네이티브 코드로 미리 컴파일할 수 있습니다. 이 경우 결과 바이너리는 Java Runtime을 실행하지 않아도 되지만 이 게시물에서는 Java 바이트 코드 기반 실행을 사용하여 몇 가지 차이점을 설명합니다. 두 언어로.

이제 우리는 Rust에서 기본 프로그램을 실행하는 방법을 살펴보았으므로 좀 더 흥미롭게 만들고 우리가 맞이하고 싶은 사람에 대해 좀 더 구체적으로 알아보겠습니다.

조금 더 정확히 알아보자

우리는 전 세계에 일반적으로 인사하는 것보다 특정 사람에게 인사함으로써 우리의 작은 예를 좀 더 흥미롭게 만들 수 있습니다. 몇 단계로 이 작업을 수행해 보겠습니다. 먼저 현재 상태에서 인사를 받는 사람을 포함하는 변수를 추출합니다 World. 인수가 제공되지 않는 경우 기본 주제로 계속 사용할 것입니다. Rust에서 이 작업을 수행하는 데는 그다지 관련이 없습니다:

let who = "World";
println!("Hello, {who}!");

보시다시피, 이것은 정말 간단합니다. 키워드 let를 사용하여 바인딩, 변수를 선언합니다 who. 두 번째 줄에서 방금 선언한 바인딩을 println! 해결하고 로 대체합니다 .{who}World

이제 남은 것은 who프로그램에 전달된 인수로 값을 재정의하는 것입니다(실제로 제공된 경우). 다시 말하지만 정말 간단해 보입니다. 대부분의 사람들이 이 글을 읽으면서 아래 Java 코드(또는 약간의 변형)를 상상할 수 있을 것이라고 확신합니다.

public class Greeter {
    
    public static void main(String... args) {

        var who = "World";
        if (args.length > 0) {
            who = args[0];
        }
        
        System.out.println("Hello, " + who + "!");
    }    
}

그러나 Rust 프로그램에서 동일한 작업을 수행하는 몇 가지 문제가 있습니다. 첫째, 논쟁의 출처는 어디입니까? 앞에서 언급했듯이 우리 main함수는 인수를 취하지 않습니다. 표준 라이브러리는 여기서 우리에게 std::env::args(). 이 함수는 개별 인스턴스 Args로 인수에 대한 반복자인 를 반환합니다.String

또 다른 문제는 Rust에서 변수 바인딩이 기본적으로 불변이라는 것입니다.  who, Java에서와 같이 프로그램에 전달된 이름을 재할당할 수 없습니다. 아니면 적어도 있는 그대로는 아닙니다. 한 가지 옵션은 바인딩을 가변으로 선언하는 것입니다. 이는 mut바인딩 선언에 키워드를 추가하여 수행됩니다.

let mut who = "World";

그러나 관용적인 Rust에서는 그렇게 하지 않을 것입니다. 이런 식으로 하지 않는 것이 실제로 좋은 이유가 있습니다. 위의 줄에서 실제로 말하는 것은 "무엇이든" who이 가리키는 것은 프로그램에 의해 변경 가능하다는 것입니다. 그러나 우리가 원하는 것은 제공된 경우에 대비하여 완전히 다른 다른 인스턴스에 바인딩하는 것입니다. 조건부 바인딩을 달성하는 몇 가지 방법이 who있습니다. if let다음 예에서 패턴을 선택했습니다 . 여기에는 몇 가지 이유가 있습니다. 그러나 지금은 Java에서 알고 있는 삼항 연산자 표현으로 생각하십시오 <booleanExpression> ? <expression1> : <expression2>(Rust에는 실제로 없는 연산자). var whoJava 버전에서 사용하면 위의 예에서 할당을 다음과 같이 리팩터링했을 것입니다 .

var who = args.length > 0 ? args[0] : "World" ;

다음은 greeter.rs예제의 모습입니다.

   

프로그램 의 첫 번째 줄 은 Java에서 패키지를 가져오는 것과 동일한 모듈use 의 를 선언합니다 . 모듈은 두 개의 콜론( )을 사용하여 구분하지만 Java는 점( )을 사용합니다. 이미 알고 있는 것과 크게 다르지 않습니다.std::env ::.

5  에서 이전 과 마찬가지로 키워드로 arguments바인딩을 선언합니다 . 그러나 이번에는 변수 이름 뒤에 콜론과 유형 정의가 옵니다. 이것이 Rust에서 특정 유형의 변수를 선언하는 방법입니다. 이 경우 컴파일러가 어떤 반복자 가 수집되어야 하는지 유추할 수 없기 때문에 유형 으로 선언 해야 합니다. 는 벡터이며 Java와 동일합니다 . 즉, 힙 할당 연속 성장 가능 배열 유형입니다. Java와 마찬가지로 Rust의 제네릭을 사용하여 벡터의 항목 유형을 선언합니다 . 러스트letwhoargumentsVec<String>StringVecArrayListVecStringStringJava에서와 같이 UTF-16이 아닌 UTF-8로 인코딩되며 변경될 수 있습니다. 마지막으로 를 채우는 방법에서 추론할 수 있듯이 Rust의 반복자는 해당 유형 Vec<String>보다 Java Streams에 더 가깝습니다 .Iterator

이제 프로그램에 전달된 모든 인수를 편리하게 사용할 수 있으므로 "World" 대신 인쇄할 항목이 실제로 제공되었는지 확인해야 합니다. 우리  우리.get(index)  Vec<String>. 그러나 그렇게 하기 전에 바인딩 검사를 수행하지 않는다는 사실에 아마 놀랄 것입니다! 인덱스가 범위 null를 벗어난 경우 반환될 것으로 예상할 수 있지만 . 그러나 Rust는 그런 일을 할 수 없었습니다. 왜냐하면 Rust에는 . 예, 당신은 그 권리를 들었습니다. null 값은 없습니다! 대신, 우리는 등을 얻습니다 . 개념적 으로 Java와 동일합니다. 그러나 Java 버전과 달리 이nullArrayList<String>nullOption<&String>Option<T>Optional<T>Option<T>적절한 Algebraic Data Type (ADT)이며 Rust는 이에 대한 일급 지원을 제공하지만 다른 게시물을 위한 것입니다. Rust에서 와 같이 선언 enum되었지만 Java에서 알고 있는 것과는 다릅니다. 그들이 상태를 가지고 있기 때문에. Option::Some(T)그러나 지금은 및 변형 이 있다는 것을 알아야 합니다 Option::None. 여기서 전자는 type 인스턴스의 존재를 나타내고 T후자는 인스턴스가 없음을 나타냅니다. 그것이 우리의 바인딩 anybody_at_all이 나타내는 것입니다. 인사할 사람이 있거나 아무도 없습니다. Rust는 카멜 케이스 대신 스네이크 케이스를 사용합니다( anybodyAtAll).

.nth(1)에서 요소를 조회한다는 것을 눈치채셨을 것입니다 Vec. 이는 Rust 인덱스가 1부터 시작하기 때문  아닙니다 . Rust는 Java와 마찬가지로 0부터 시작하는 인덱싱을 사용합니다. 그러나 벡터의 첫 번째(ie .nth(0)) 요소는 시작 시 호출된 실제 바이너리 이름이므로 아마도 ./greeter작업 디렉토리에 바이너리가 포함되어 있을 것입니다.

그리고 마지막 &으로 . Rust의 앰퍼샌드는 참조 를 정의합니다 . null이 없기 때문에 우리는 a가 어딘가 를 가리킨다는 것을 압니다. a , 즉 실제 String 값을 포함하는 벡터를 생성한 5 행 에서 수행한 것과는 달리 여기서는 해당 벡터 에 대한 참조  반환합니다 . 해당 문자열의 복사가 발생하지 않으며 남아 있는 소유권은 . Rust 는 실제로 String에 대한 참조인 Java와 동일합니다.StringOption<&String>&StringStringVec<String>.getOptionStringStringarguments&StringString

9행 은 표현식 을 사용하여 의 조건부 할당이 who발생하는 곳입니다. if let이 다소 이상한 코드 라인에서 다시 몇 가지 일이 진행되고 있습니다. 그래서 그것을 조금 분해합시다. 먼저 if let Some(one) = anybody_at_all 표현식 anybody_at_all은 a 인지 여부를 테스트합니다 Option::Some(T). 그렇다면 첫 번째 분기가 평가되고 그렇지 않으면 분기가 평가됩니다 else. 전자의 경우 if let문은 또한 Rust에서 디스트럭처링(destructuring)이라고 알려진 것을 수행합니다. 같음의 왼쪽은 일치할 것으로 예상되는 형식을 나타냅니다. one에서 값을 바인딩할 변수를 선언 합니다 Option::Some. if let그런 다음 해당 변수는 분기 범위 내에서 사용할 수 있게 됩니다 .

10번째 줄 도 아마 다소 놀랍거나 적어도 12번째 줄 만큼이나 놀랍습니다 . 둘 다 끝에 세미콜론( ;)이 없습니다. Rust에서는 거의 모든 것이 표현식입니다. 그러한 표현식이 무엇을 평가하는지 정의하기 위해 세미콜론이 없을 때 블록의 마지막 문이 표현식이 평가하는 값이 됩니다. 따라서 이 두 줄이 수행하는 작업은 반환 one(인수 목록에서 읽은 문자열 값에 대한 참조, 에 저장됨 arguments) 또는 표현식 "World"에서 반환하는 것입니다. 그런 다음 해당 값은 9행 의 표현식 앞에 선언한 if let바인딩과 연결됩니다 . 반면 13행 에는 뒤에 세미콜론이 있습니다. 의 끝을 표시합니다whoif letwho 변수 에 할당하는 let 문 . "작은" 리팩토링의 시작 부분에서 했던 것처럼 마침내 15행 에서 ​​인사할 준비가 되었습니다 …

$ rustc greeter.rs

$ ./greeter
Hello, World!

반면에 이제 우리 프로그램에 인수를 제공한다면… 드럼롤!

$ ./greeter John
Hello, John!

효과가있다! 글쎄요, 쉬웠죠?

let if표현이 다소 이상 하다면 사용할 수 있는 다른 솔루션이 있습니다. Rust에서도 매우 흔한 것 중 하나인 match문입니다. using match은 Java의 표현식에 가깝고 switch이전 예제와 마찬가지로 패턴 일치 및 구조 분해를 사용합니다. 다음과 같이 표시됩니다.

let who = match anybody_at_all {
    Some(one) => one,
    None => "World",
};

match식은 실제로 식일 뿐이므로 다시 할당하는 데 사용 합니다 who. let if식 tho 와 달리 식은 모든 변형 match에 대해 철저해야 합니다 .  Option가 2개 뿐이므로 큰 차이가 없으므로 다시 2개의 분기로 끝납니다. 하지만 이번에는 블록을 선언할 필요가 없습니다. 우리는 각각 의 분기가 표현식 2를 평가하는 값이 되도록 합니다.Option::Some(T)Option::Nonematch

우리 식과 실제로 동등한 것을 작성하려면 다음 과 같이 에 대해 명시적으로 일치하는 대신 밑줄을 let if사용하여 catch all을 our 의 마지막 분기로 사용할 수 있습니다 .matchNone_ ⇒ "World"

문자열은 의 "단지" 인스턴스입니다 String. 맞습니까?

우리는 위의 예에서 두 가지 중요한 개념 , 즉 선행 앰퍼샌드 문자 String로 표시되는 유형 및 참조를 소개했습니다. &위의 예에서 서로 다른 문자열에 어떤 일이 일어나는지 좀 더 자세히 살펴보겠습니다. 우리는 Rust 컴파일러가 우리를 위해 많은 유형을 추론할 수 있고 유형을 명시적으로 선언해야 하는 부담을 제거할 수 있음을 보았습니다. 우리는 여전히 이러한 유형을 명시적으로 선언할 수 있으며 이는 코드를 읽는 프로그래머에게 도움이 될 수 있으며 여기에는 우리 자신도 포함됩니다. 여기 이 예가 그러한 경우일 수 있으므로 다음과 같이 진행합니다.

   

가능한 경우 명시적 유형을 포함할 뿐만 아니라 인수가 제공되지 않았을 때 환영하는 everyone기본값인 새 변수도 있도록 코드가 약간 변경되었습니다. 4행 에서 "World"볼 수 있듯이 는 no 이지만 대신 어떤 유형입니다!? 앰퍼샌드는 지금까지 친숙할 것입니다. 참조입니다. 그러나 ? a가 무엇인지 설명하기 전에 먼저 프로그램을 컴파일할 때 어떤 일이 발생하는지 이해하겠습니다 . (참고용이니 기억하시죠?) 값 은 참으로 특별합니다. 프로그램에 제공된 인수와 달리"World"String&strstrstr"World"everyone"World"소스 코드에 바로 있기 때문에 컴파일 시간에 알 수 있습니다. 실제 값은 컴파일되는 결과 바이너리(이 경우 실행 파일) 내에 존재합니다. 프로그램이 메모리에 로드되면 "World". 그리고 everyone메모리의 해당 위치를 가리켜야 합니다. 그것은 참조를 설명하지만 str대신 String!

. _ everyone_ String우선 String 인스턴스는 Rust에서 변경 가능합니다. 이 문자열은 변경하면 안 됩니다. 즉, rustc는 이미 해당 참조를 변경 가능하게 만드는 것을 금지 &mut str합니다. 또한 a String는 항상 할당된 힙입니다. 실제로 Vec는 UTF-8 인코딩 문자열을 구성하는 바이트를 포함하는 에 의해 지원됩니다. 반면 an str은 유효한 UTF-8 바이트 시퀀스로의 슬라이스, 보기입니다. 때로는 문자열 슬라이스 라고도 합니다 . str당신은 실제로 직접 사용하지 않습니다 . 그러나 오히려 참조를 통해. An 은 컴파일 타임에 크기를 알 수 없는 str것으로도 알려져 있습니다 . !Sized참조는 해당 정보를 추가하고 실제 문자열의 길이 정보를 포함하는 것입니다.

그래서 우리 는 바이트 가 존재 everyone: &str하는 메모리의 어떤 위치를 가리키고 있습니다. World이것들은 바로 다른 문자열 값이나 쓰레기가 뒤따를 수 있습니다. 그것이 우리의 실제 str. 참조는 해당 위치와 그것이 가리키는 문자열의 길이를 인코딩합니다. 이 특별한 경우에는 5가 됩니다. 반면에 String인스턴스는 힙 할당되고 벡터를 포함하며, 벡터는 연속된 메모리 위치, 용량 및 현재 길이에 대한 포인터입니다. env::args()이것이 함수 호출 에 의해 프로그램의 인수가 사용 가능한 방식 입니다.

그런 다음 이러한 주장을 살펴보겠습니다. 현재 우리는 그것들을 모두 수집하여 6행arguments: Vec<String> 에 넣습니다 . 나중에 인쇄할 값에 대한 참조만 사용할 것이기 때문에 이렇게 합니다. 가치 자체가 아닙니다. 여기에 Rust와 Java의 또 다른 중요한 차이점이 있습니다. 가비지 수집기가 없습니다. 단점은 이제 유효한 항목에 대한 참조점이 필요하다는 것 입니다. 그리고 그 무언가는 더 이상 필요하지 않을 때 결국 해제되어야 합니다. 하지만 우리 코드를 보면 가비지 수집 언어가 아닌 다른 언어에서 알 수 있는 유사한 것이 없습니다 .freedelete

Rust는 C++ 개발자에게 잘 알려진 관용구를 사용합니다: Resource Acquisition Is Initialization 을 나타내는 RAII . 컴파일러는 객체가 범위를 벗어날 때 해제되는지 확인합니다(또는 Rust가 호출하는 대로 -ped). 위의 예에서 이는 함수가 끝날 때 우리를 생성하여 할당된 메모리 도 해제되고 누출되지 않음을 의미합니다. 그리고 인수가 제공되었을 때 사용자에게 메시지를 인쇄할 때 코드에서 해당 벡터에 대한 참조를 사용하므로 이것이 필요합니다. Rust 컴파일러는 모든 참조가 유효한 것을 가리키는 경우에만 프로그램이 컴파일되도록 합니다. 예를 들어 다음 과 같이 변수 를 인라인하여 몇 줄의 코드를 저장하려고 했습니다 .Dropmainlet arguments: Vec<String>arguments

let anybody_at_all = env::args().collect::<Vec<String>>().get(1);

코드가 컴파일되지 않습니다. 그것의 문제는 the가 아마도 (the )를 가리키는 인수의 값 Vec<String>을 소유 하는 the 가 생성되자마자 해제되어 참조가 유효하지 않을 수 있는 것을 가리킬 수 있다는 것입니다. 컴파일은 문제뿐만 아니라 수정 사항도 설명하는 멋진 오류 메시지를 제공합니다.anybody_at_all&String

$ rustc greeter.rs
error[E0716]: temporary value dropped while borrowed
 --> greeter.rs:6:43
  |
6 |     let anybody_at_all = env::args().collect::<Vec<String>>().get(1);
  |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       - temporary value is freed at the end of this statement
  |                          |
  |                          creates a temporary which is freed while still in use
7 |
8 |     let who: &str = if let Some(one) = anybody_at_all {
  |                                        -------------- borrow later used here
  |
help: consider using a `let` binding to create a longer lived value
  |
6 ~     let binding = env::args().collect::<Vec<String>>();
7 ~     let anybody_at_all: Option<&String> = binding.get(1);

이제 첫 번째 인수만 사용하므로 모든 인수를 메모리에 유지할 필요가 없습니다. 다음은 이를 수행하는 몇 가지 코드입니다. 먼저 첫 .skip(n)번째 항목을 -ping하고 다음 항목만 유지합니다. Option<String>다시 말하지만, 없을 수도 있으므로 를 반환합니다 . 이것이 첫 번째 줄입니다.

let anybody_at_all: Option<String> = env::args().skip(1).next();
let who = anybody_at_all.as_ref().map_or("World", String::as_str);

두 번째 줄에서 우리는 약간의 춤을 춥니다. 먼저 우리는 를 호출 .as_ref()합니다. 그러면 , 즉 에 대한 가능한 참조를 Option<String>반환 합니다 . 마지막 호출 은 두 가지 중 하나를 수행합니다. 값이 없는 경우 첫 번째 인수를 반환합니다. 즉, 유형이 있거나 값이 있는 경우 실제 값에 대한 메서드를 호출하여 매핑합니다 , 문자열에 a를 반환 합니다.Option<&String>String.map_or()"World"&str.as_str()&str

이제 이전 버전에서 명시적으로 one: &String" 매핑"할 필요가 없는 이유가 궁금할 것 입니다.&str

let who: &str = if let Some(one) = anybody_at_all {
    one
} else {
    everyone
};

 10행 이 읽히지 one.to_str()않습니까? 컴파일러는 실제로 우리를 위해 변환을 처리합니다. .to_str()그래도 using이 아니라 Java .deref()에서 인터페이스에 가장 가까운 특성인 특성에서 오는 using, std::ops::Deref. 따라서 10행  one.deref()암시적으로 됩니다. 이 메커니즘을 Deref강제 라고 합니다 . use명시적으로 직접 수행하려면 파일 맨 위에 해당 특성에 대한 설명 을 추가해야 합니다.

또는 명시적 표기법을 사용하여 를 사용하여 &String 를 로 String, 저것으로 역 str참조한 다음 를 사용하여 해당 참조를 반환합니다 &**one. 그것은 꽤 이상한 구문입니다. clippyRust linter는 실제로 다음을 사용하지 못하게 할 것입니다.

warning: deref which would be done by auto-deref
  --> greeter.rs:10:9
   |
10 |         &**one
   |         ^^^^^^ help: try this: `one`
   |

clippy위의 내용은 프로젝트 에 사용해야 하는 많은 이유 중 하나 입니다. 그것은 관용적 코드를 작성하는 Rust 방식에 익숙해지는 데 최소한 도움이 될 것입니다.

요약하자면

fn키워드 를 사용하여 Rust에서 함수를 선언하는 방법을 살펴보았습니다 . 함수는 Java의 정적 메서드와 동일하지만 클래스에 바인딩되지 않습니다. let키워드 를 사용하여 변수를 선언 했습니다. 대부분의 경우 컴파일러에서 유형을 유추할 수 있지만 변수 이름 :뒤에 콜론과 유형을 접미사로 추가하여 해당 유형 정보를 명시적으로 제공할 수 있습니다.

std::Option우리는 Rust 에서 enum우리가 Java에서 알고 있는 것과 다르다는 것을 보았습니다. 우리는 let if및 match문을 사용하여 몇 가지 방법으로 이들에 대해 패턴 일치를 수행했으며 조건부로 변수에 할당하기 위해 노출되는 보다 기능적인 스타일 API를 활용했습니다.

마지막으로 프로그램에 제공된 첫 번째 인수를 수집할 때 데이터가 있는 위치와 수명 주기에 대해 조금 더 생각했습니다. 두 가지 다른 유형의 문자열을 소개 했습니다. 참조 및 역 참조로 재생되었습니다 String.str

이 모든 것을 통해 여러분이 직접 Rust 실험을 시작할 수 있기를 바랍니다. 작은 프로젝트를 부트스트래핑하여 오늘 시작하는 것이 어떻습니까?

$ cargo new playground
     Created binary (application) `playground` package

$ cd playground

$ find *
Cargo.toml
src
src/main.rs

$ cat src/main.rs
fn main() {
    println!("Hello, world!");
}

$ cargo run
   Compiling testing v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.78s
     Running `target/debug/playground`
Hello, world!

여기를 보세요, Hello, world!… 원점으로 돌아가세요! 그러나 그것은 당신을 시작할 것입니다. 빌드 도구 cargo는 도구 체인, Java의 maven 또는 gradle 등가물 및 Cargo.toml프로젝트의 매니페스트의 일부입니다.

시작하는 경우 cargo clippy코드 기반에서 정기적으로 실행하십시오. 다른 clippy와는 달리, 이 클립이 유용할 것이라고 약속합니다! 재미있을 것 같은 프레젠테이션 도 했습니다. 피드백이나 질문이 있으시면 알려주세요. 귀하에게 불분명한 사항이 있다면 다른 사람에게도 해당될 수 있습니다! 그리고 더 많은 것을 원하신다면 이 시리즈의 다음 게시물인 소유권, 차용 검사기 및 몇 가지 더 많은 내용을 계속 진행하세요!

 

 

 

https://wcgw.dev/posts/2023/rusty-java-intro/

728x90
728x90
반응형

Golang에서 사용하는 Standard Library 들이다. 갯수가 많지가 않아 초기 학습 곡선이 크게 높지는 않다. 하지만 하위 정보들이 별도로 있기 때문에 위에 나열한것만 있는건 아니다. 그렇지만 기본적으로 세팅된 1레벨 키워드로 봐서는 크게 많은게 아니니 학습하는데 너무 부담가지지 말자.

 

모든 언어들이 그렇지만 쉽게 배울 수 있다고 얘기하는 정보에 속지 말아야 한다. 어떤 언어든 깊숙히 들어가게 되면 거의 비슷한 난이도를 가진다. Javascript도 VBScript도 Python 도 초기 진입장벽이 낮다는거지 언어의 깊은부분까지 쉽다고 얘기하는건 아니다.

728x90
728x90
반응형

Rust는 어떤 면에서는 굉장합니다. 그러나 빠르게 움직여야 하는 스타트업을 선택하기 전에 두 번 고려하세요.


나는 프로그래밍 언어에 대한 성전을 시작하거나 시작하고 싶지 않기 때문에 이 글을 쓰는 것을 주저했습니다. (불길한 미끼를 없애기 위해 Visual Basic은 최고의 언어입니다!) 그러나 많은 사람들이 Rust에 대한 나의 경험과 프로젝트에 Rust를 선택해야 하는지 여부를 묻습니다. 그래서 저는 빠르게 움직이고 팀을 확장하는 것이 정말 중요한 스타트업 환경에서 Rust를 사용하는 것에 대해 제가 본 장단점을 공유하고 싶습니다.

나는 특정한 것에 대해 Rust의 팬임을 분명히 하고 싶습니다 . 이 게시물은 Rust가 언어로서 얼마나 나쁜지에 대한 것이 아닙니다. 그러나 내가 말하고 싶은 것은 Rust를 사용하는 것이 당신이 빨리 움직이려고 한다면 주요 요인이 될 수 있는 사소하지 않은 생산성 타격을 거의 확실하게 수반할 것이라는 점입니다. 속도 영향이 회사 및 제품에 대한 언어의 이점만큼 가치가 있는지 신중하게 평가하십시오.

바로 앞에, Rust는 설계된 일에 매우 뛰어나며 프로젝트에 Rust의 특정 이점(고성능, 초강력 타이핑, 가비지 수집이 필요 없는 시스템 언어 등)이 필요한 경우에 말해야 합니다. 그런 다음 Rust는 훌륭한 선택입니다. 그러나 저는 Rust가 그다지 적합하지 않은 상황에서 자주 사용되며 팀은 많은 이점을 얻지 못한 채 Rust의 복잡성과 오버헤드의 대가를 지불한다고 생각합니다.

Rust에 대한 나의 주된 경험은 이전 스타트업에서 2년 조금 넘게 일하면서 얻은 것입니다. 이 프로젝트는 클라우드 기반 SaaS 제품으로, 거의 기존의 CRUD 앱에 불과했습니다. 데이터베이스 앞에 REST 및 gRPC API 엔드포인트를 제공하는 마이크로서비스 집합과 기타 일부 백-서비스입니다. 최종 마이크로서비스(자체는 Rust와 Python의 조합으로 구현됨). Rust는 주로 회사 창립자 두 명이 Rust 전문가였기 때문에 사용되었습니다. 시간이 지나면서 우리는 팀을 상당히 성장시켰고(엔지니어링 인원을 거의 10배 늘림) 코드베이스의 크기와 복잡성도 상당히 커졌습니다.

팀과 코드베이스가 성장함에 따라 시간이 지남에 따라 Rust를 계속 사용하는 데 점점 더 많은 세금을 내고 있다고 느꼈습니다. 때때로 개발이 느렸고, 새로운 기능을 출시하는 데 예상보다 오래 걸렸으며, 팀은 Rust를 사용하기로 한 초기 결정으로 인해 실질적인 생산성 저하를 느꼈습니다. 다른 언어로 코드를 다시 작성하면 장기적으로 개발이 훨씬 더 민첩해지고 전달 시간이 빨라지지만 주요 재작성 작업을 위한 시간을 찾는 것은 매우 어려웠을 것입니다. 그래서 우리는 총알을 깨물고 많은 양의 코드를 다시 작성하기로 결정하지 않는 한 Rust에 갇혀 있었습니다.

녹은 얇게 썬 빵 이후로 가장 좋은 것으로 여겨졌는데 왜 우리에게는 잘 작동하지 않았습니까?

Rust는 엄청난 학습 곡선을 가지고 있습니다.

나는 내 경력에서 수십 개의 언어로 작업했으며 거의 ​​예외 없이 대부분의 현대적이고 절차적인 언어(C++, Go, Python, Java 등)는 모두 기본 개념 측면에서 매우 유사합니다. 각 언어에는 차이점이 있지만 일반적으로 언어마다 다른 몇 가지 주요 패턴을 학습한 다음 매우 빠르게 생산적일 수 있습니다.그러나 Rust를 사용하려면 배워야 합니다.완전히 새로운 아이디어— 수명, 소유권 및 차용 검사기와 같은 것. 이는 다른 공통 언어로 작업하는 대부분의 사람들에게 친숙한 개념이 아니며 숙련된 프로그래머에게도 상당히 가파른 학습 곡선이 있습니다.

이러한 "새로운" 아이디어 중 일부는 물론 다른 언어, 특히 기능적인 언어에 존재하지만 Rust는 이러한 아이디어를 "주류" 언어 설정으로 가져오므로 많은 Rust 초보자에게 생소할 것입니다.

내가 함께 일했던 가장 똑똑하고 경험이 많은 개발자였음에도 불구하고 팀의 많은 사람들(나 자신 포함)은 Rust에서 특정 작업을 수행하는 정식 방법, 컴파일러에서 종종 난해한 오류 메시지를 파악하는 방법 또는 핵심 라이브러리가 작동하는 방식을 이해하는 방법(자세한 내용은 아래 참조). 우리는 팀이 지식과 ​​전문 지식을 공유하는 데 도움이 되도록 매주 "Rust 학습" 세션을 갖기 시작했습니다. 모두가 느린 개발 속도를 느꼈기 때문에 이것은 모두 팀의 생산성과 사기를 크게 떨어뜨렸습니다.

소프트웨어 팀에서 새로운 언어를 채택하는 것이 어떤 것인지 비교하기 위해 Google의 한 팀은 C++에서 Go로 완전히 전환한 최초의 팀 중 하나였습니다. 15명 정도의 팀이 처음으로 Go로 꽤 편안하게 코딩했습니다. Rust를 사용하면 매일 몇 달 동안 언어로 작업한 후에도 팀의 대부분의 사람들이 완전히 유능하다고 느끼지 못했습니다. 많은 개발자들이 자신의 기능이 착륙하는 데 예상보다 오래 걸리고 Rust를 이해하는 데 너무 오랜 시간을 소비하고 있다는 사실에 종종 당혹스럽다고 말했습니다.

Rust가 해결하려는 문제를 해결하는 다른 방법이 있습니다.

위에서 언급했듯이 우리가 구축한 서비스는 상당히 간단한 CRUD 앱이었습니다. 이 서비스에 대한 예상 부하는 이 특정 시스템의 수명 동안 최대 초당 몇 개의 쿼리를 넘지 않을 것입니다. 이 서비스는 실행하는 데 많은 시간이 걸릴 수 있는 상당히 정교한 데이터 처리 파이프라인의 프런트엔드였으므로 서비스 자체가 성능 병목 현상이 될 것으로 예상되지 않았습니다. Python과 같은 기존 언어가 좋은 성능을 제공하는 데 문제가 있을 것이라는 특별한 우려는 없었습니다. 웹 대면 서비스가 처리해야 하는 것 외에 특별한 안전이나 동시성 요구 사항은 없었습니다. 우리가 Rust를 사용한 유일한 이유는 시스템의 원래 작성자가 Rust 전문가였기 때문이지 이런 종류의 서비스를 구축하는 데 특히 적합했기 때문이 아닙니다.

Rust는 개발자 생산성보다 안전이 더 중요하다는 결정을 내렸습니다. 이것은 OS 커널에서 코드를 작성하거나 메모리가 제한된 임베디드 시스템과 같은 많은 상황에서 올바른 절충안이지만 모든 경우에 올바른 절충안이라고 생각하지 않습니다. 특히 속도가 중요한 스타트업에서는 그렇지 않습니다. 나는 실용주의자입니다. 팀의 모든 구성원이 이러한 문제를 완전히 방지하도록 설계된 언어를 사용하여 생산성이 4배 저하되는 것보다 팀에서 가끔 발생하는 메모리 누수 또는 Python 또는 Go로 작성된 코드의 유형 오류를 디버깅하는 데 시간을 할애하는 편이 낫 습니다 . .

위에서 언급했듯이 Google 팀은 전적으로 Go로 서비스를 구축했으며 시간이 지남에 따라 8억 명 이상의 사용자와 Google 검색 QPS의 4배 정도를 지원하는 수준으로 성장했습니다. 이 서비스를 구축하고 실행하는 동안 Go의 유형 시스템이나 가비지 컬렉터로 인해 발생한 문제에 부딪힌 횟수는 한 손으로 셀 수 있습니다. 기본적으로 Rust가 피하도록 설계된 문제는 좋은 테스트, 좋은 린팅, 좋은 코드 검토, 좋은 모니터링 등 다른 방법으로 해결할 수 있습니다. 물론 모든 소프트웨어 프로젝트가 이러한 사치를 누리는 것은 아니므로 다른 상황에서는 Rust가 좋은 선택이 될 수 있다고 상상할 수 있습니다.

Rust 개발자를 고용하는 데 어려움을 겪을 것입니다.

우리는 이 회사에 있는 동안 수많은 사람들을 고용했지만 엔지니어링 팀에 합류한 60명 이상의 사람들 중 약 2~3명만이 Rust에 대한 이전 경험이 있었습니다. 이것은 Rust 개발자를 찾기 위한 노력이 부족해서가 아닙니다 — 그들은 단지 거기에 없을 뿐입니다. (동일한 이유로 우리는 Rust 로만 코딩하기를 원하는 사람들을 고용하는 것을 주저했습니다 . 언어 및 기타 기술 선택이 민첩한 방식으로 이루어져야 하는 스타트업 환경에서 설정하는 것은 나쁜 기대라고 생각하기 때문입니다.) Rust가 더 주류가 됨에 따라 Rust 개발 인재의 수는 시간이 지남에 따라 변할 것입니다.

또 다른 두 번째 요인은 Rust를 사용하면 팀에서 Rust를 아는 사람들과 그렇지 않은 사람들 사이에 분열이 생길 것이 거의 확실하다는 것입니다. 우리는 이 서비스를 위해 "난해한" 프로그래밍 언어를 선택했기 때문에 기능 구축, 생산 문제 디버깅 등에 도움이 되었을 수 있는 회사의 다른 엔지니어들은 대체로 도움을 줄 수 없었습니다. Rust 코드베이스의 꼬리. 엔지니어링 팀의 이러한 대체 가능성 부족은 빠르게 움직이고 팀의 모든 구성원의 결합된 강점을 활용하려고 할 때 실질적인 문제가 될 수 있습니다. 내 경험상, 사람들은 일반적으로 C++와 Python 같은 언어 사이를 이동하는 데 거의 어려움이 없지만 Rust는 충분히 새롭고 복잡해서 사람들이 함께 작업하는 데 장벽이 됩니다.

라이브러리와 문서는 미성숙합니다.

이것은 (희망합니다!) 시간이 지나면 해결되겠지만 Go와 비교할 때 Rust의 라이브러리와 문서화 생태계는 믿을 수 없을 정도로 미숙합니다. 이제 Go는 전 세계에 출시되기 전에 Google의 전체 전담 팀에서 개발하고 지원했다는 이점이 있으므로 문서와 라이브러리가 상당히 세련되었습니다. 이에 비해 Rust는 오랫동안 진행 중인 작업처럼 느껴졌습니다. 많은 인기 있는 라이브러리에 대한 문서가 매우 드물고 사용 방법을 이해하기 위해 특정 라이브러리 의 소스 코드 를 읽어야 하는 경우가 많습니다 . 이것은 나쁘다.

팀의 Rust 사과론자들은 종종 "async/await는 여전히 정말 새롭습니다." 및 "예, 해당 라이브러리에 대한 문서가 부족합니다."와 같은 말을 하지만 이러한 단점은 팀에 상당한 영향을 미쳤습니다. 우리는 서비스의 웹 프레임워크로 Actix를 채택함으로써 초기에 큰 실수를 저질렀습니다. 그 결정은 아무도 수정 방법을 알아낼 수 없는 라이브러리 깊숙이 묻혀 있는 버그와 문제에 부딪히면서 엄청난 고통과 괴로움을 초래했습니다. (공평하게 말하면 이것은 몇 년 전의 일이고 지금은 상황이 개선되었을 수 있습니다.)

물론 이런 종류의 미숙함은 실제로 Rust에만 국한된 것은 아니지만 팀이 지불해야 하는 세금에 해당합니다. 핵심 언어 문서와 자습서가 아무리 훌륭하더라도 라이브러리 사용 방법을 알 수 없다면 별 문제가 되지 않습니다(물론 처음부터 모든 것을 작성하려는 경우가 아니라면).

Rust는 새로운 기능을 대략적으로 만드는 것을 매우 어렵게 만듭니다.

나는 다른 사람에 대해 모르지만 적어도 나에게는 새로운 기능을 구축할 때 일반적으로 모든 데이터 유형, API 및 기타 세부 정보가 먼저 작동하지 않습니다. 나는 종종 몇 가지 기본 아이디어가 작동하도록 노력하고 일이 어떻게 작동해야 하는지에 대한 내 가정이 다소 정확한지 확인하려고 코드를 작성합니다. 예를 들어 Python에서 이 작업을 수행하는 것은 매우 쉽습니다. 타이핑과 같은 작업을 빠르고 느슨하게 수행할 수 있고 아이디어를 대략적으로 작성하는 동안 특정 코드 경로가 손상되더라도 걱정할 필요가 없기 때문입니다. 나중에 돌아가서 모든 것을 깔끔하게 만들고 모든 유형 오류를 수정하고 모든 테스트를 작성할 수 있습니다.

Rust에서 이런 종류의 "초안 코딩"은 매우 어렵습니다. 왜냐하면 컴파일러 는 유형 및 수명 검사를 통과하지 못하는 빌어먹을 모든 것에 대해 불평할 수 있고 불평할 것이기 때문 입니다. 이것은 생산 준비가 된 최종 구현을 구축해야 할 때 완벽하게 이해되지만 아이디어를 테스트하거나 기본 기반을 마련하기 위해 무언가를 함께 짜내려고 할 때 절대적으로 짜증납니다. 매크로는 어느 정도 도움 이 unimplemented!되지만 여전히 컴파일하기 전에 스택에서 모든 유형을 확인해야 합니다.

정말 짜증나는 것은 로드 베어링 인터페이스의 유형 서명을 변경해야 하고 유형이 사용되는 모든 장소를 변경하는 데 시간을 소비하여 무언가에 대한 초기 찌르기가 가능한지 확인하는 것입니다. 그런 다음 다시 변경해야 한다는 것을 깨달았을 때 모든 작업을 다시 실행합니다.

러스트는 무엇을 잘하나요?

Rust에 대해 제가 좋아하는 점과 다른 언어로 갖고 싶은 Rust의 기능이 분명히 있습니다. match구문이 훌륭합니다 . ,  특성은 정말 강력하며 Option연산자 는 오류를 처리하는 우아한 방법입니다. 이러한 아이디어 중 많은 부분이 다른 언어로 된 대응물을 가지고 있지만 Rust의 접근 방식은 특히 우아합니다.ResultError?

나는 높은 수준의 성능과 안전성이 필요하고 빠르게 성장하는 전체 팀과 함께 코드의 주요 부분을 빠르게 발전시켜야 할 필요성에 대해 크게 걱정하지 않는 프로젝트에 Rust를 절대적으로 사용할 것입니다. 개별 프로젝트나 매우 작은(예: 2-3명) 팀의 경우 Rust가 적합할 것입니다. Rust는 성능과 안전이 가장 중요한 커널 모듈, 펌웨어, 게임 엔진 등과 같은 것들과 출하 전에 철저한 테스트를 수행하기 어려울 수 있는 상황에서 훌륭한 선택입니다

[원본]

https://mdwdotla.medium.com/using-rust-at-a-startup-a-cautionary-tale-42ab823d9454

728x90
728x90
반응형

글을 읽는 분들에게 미안한 마음이 들지만 앞단에 진행되던 내용들도 뭐 나름 의미가 있기는 하다.
003 글 마지막에 template 기본을 만들어주는 기능을 발견하고 바로 작성하던 글을 종료시켰다. 그럼 새롭게 알아낸 방법으로 template를 만들어보기로 하자.
https://wails.io/docs/guides/templates/

Templates | Wails

Wails generates projects from pre-created templates. In v1, this was a difficult to maintain set of projects that were

wails.io

01. 템플릿 기본 생성하기

아주 간단히 template 기본 틀을 생성할 수 있다.

wails generate template -name {프로젝트명}
ex)
wails generate template -name wails-vite-sveltekit-ts-tailwind-template

앞에서 등록한 글들에서 본 폴더/파일 구조와 별반 다르지 않다고 느낄 수 있는데 자세히 보면 설정 파일들의 파일명이 다르다는 걸 알 수 있다.
package.tmpl.json
, app.tmpl.go
, go.tmpl.go
, main.go.tmpl
, wails.tmpl.json
이렇게 tmpl이라는 문구가 추가되어 생성되어 있다.

wails.tmpl.json

대표적으로 wails.tmpl.json 파일을 오픈해보면 변경될만한 정보들이 {{ }} 로 감싸져 있는 걸 볼 수 있다. 이 부분이 -t 옵션으로 프로젝트를 생성하게 되면 상황에 맞는 값들이 대입되어 온전한 wails.json 파일이 생성되게 된다.
그런데 template 프로젝트를 생성 후 frontend 폴더를 보면 frontend 소스가 없고 dist 폴더와 package.tmpl.json 만 있는 걸 볼 수 있다. dist는 frontend 폴더에 개발을 하면 빌드 시 재 구성되는 부분이라 확인할 필요가 없고 package.tmpl.json은 개발에 사용할 의존 모듈들에 대한 정의들이 정의된 설정 파일이다. 개발된 샘플 소스가 없다.
template 프로젝트를 생성할 때 -frontend 옵션이 있는데 별도로 구성된 javascript 프로젝트를 frontend로 포함시켜서 템플릿을 완성시켜 주는 기능이 포함되어있다.
나는 sveltekit + vite +typescript + tailwind 를 사용할 예정이므로 이 네 가지를 적용한 프로젝트를 별도로 만들어보겠다.

일단 앞에서 샘플로 생성한 폴더를 삭제한다.

rmdir /s /q wails-vite-sveltekit-ts-tailwind-template

02. Frontend 기술 적용된 프로젝트 만들기
https://kit.svelte.dev/docs/introduction

Introduction • Docs • SvelteKit

Introduction Edit this page on GitHub SvelteKit is in release candidate phase for 1.0 while we address reported issues and add polish. If you get stuck, reach out for help in the Discord chatroom. See the migration guides for help upgrading from Sapper. Sv

kit.svelte.dev

sveltekit을 설치하기 위해 사이트에 접속해 보자.
그럼 다음 내용을 확인할 수 있다.

나는 npm을 사용하지 않고 pnpm을 사용할 것이므로 위 내용과는 조금 다르게 프로젝트를 생성하겠다.

pnpm create svelte@latest sveltekit-template

문답형 설치가 진행된다.

Skeleton Project로 템플릿을 생성하니 너무 없어 보여서 SvelteKit demo app으로 선택하기로 한다.

Typescript 를 사용할 예정이므로 두 번째 항목을 선택한다 (본인이 만들 템플릿에 따라서 선택하면 된다. No는 VanillaJS를 의미한다.)

ESLint (Option)
Prettier (Option)
Playwright (Option)
위 세 가지 선택 여부를 결정하고 프로젝트를 생성한다. (위 3가지는 검색해서 한번 알아보도록 하자)

프로젝트가 생성되면서 다음 절차를 통해서 실행해볼 수 있다는 문구가 나타난다. (npm을 pnpm으로 바꾸고 pnpm에서는 run 명령어를 생략해도 되므로 run을 생략하고 실행해보자(4 번에서)
그리고 git 사용 여부는 본인 선택이므로 사용할 거면 git 설치 부분을 학습하고 설치 후 3번을 진행하도록 한다. 단 지금은 템플릿을 만드는 단계이므로 맨 마지막에 진행해도 상관없을 것 같으므로 지금은 3번은 스킵하도록 한다.

cd sveltekit-template
pnpm install
pnpm dev -- --open

pnpm install 실행 중 corepack을 통한 update를 하라고 하는데 지금 정확히 잘 모르겠다. 추후 알게 되면 해당 글에 업데이트하도록 하겠다.

다음과 같이 devDependencies에 의존성 모듈들이 추가되었다고 나타난다. 모두 최신 버전인 것 같다.
(Skeleton 으로 생성했을 때와는 의존성 모듈 구성이 조금 틀리니 이상하다고 겁먹지 말자)
이제 정상적으로 구동이 되는지 실행해보자.
pnpm dev -- --open

웹서버가 구동되었다

크롬 브라우저를 실행 후 Local에 있는 주소를 복사해서 붙여 넣어 보자.


정상적으로 실행되는 걸 확인했다.
이번에 사용할 기술들 중 3가지가 해결되었다.
Sveltekit + Vite + Typescript
다음으로 Tailwind css 프레임워크를 추가해보자.
https://tailwindcss.com/docs/guides/sveltekit

Install Tailwind CSS with SvelteKit - Tailwind CSS

Documentation for the Tailwind CSS framework.

tailwindcss.com

8 단계로 설명해 놓았는데 1단계는 이미 처리된 거고 2단계부터 적용해보자

항상 유의해야 할 부분이 npm을 사용하지 않고 pnpm을 사용하는 거다.

pnpm install -D tailwindcss postcss autoprefixer svelte-preprocess

devDependencies가 추가되었는데 svelte-preprocess 부분은 이미 등록되어 있어서 누락되었다.

pnpx tailwindcss init tailwind.config.cjs -p

두 가지 cjs 파일이 생성되었다.
이제 사용할 모든 프레임워크와 모듈이 추가되었으므로 wails 템플릿을 다시 만들어보자.

02. frontend Project로 wails template 생성하기

cd ..
wails generate template -name wails-vite-sveltekit-ts-tailwind-template -frontend ./sveltekit-template

sveltekit-template 폴더 상위로 이동한 후 template 을 생성해보자.


정상적으로 생성되었다.
알기 전까진 꽤 어렵게 생각되었는데 구조를 알고 나니 큰 어려움 없이 사용자 Template를 생성할 수 있었다.
그렇지만 아직 끝난 게 아니다.
일단 템플릿 형태이기 때문에 wails build 해서 바로 실행할 수 있는 구조가 아니다 . wails 의 -t 옵션을 통해서 프로젝트를 생성해 줘야 정상적인 값이 설정된 프로젝트가 생성되기 때문이다.
그리고 wails로 template를 생성하면 기본적으로 npm을 사용하는 구조로 생성된다.
그래서 wails.tmpl.json 파일을 열어서 npm 부분을 수정해주자.

다른 템플릿들을 보면

다음과 같은 옵션들이 있는데 추가해주자 : 추가하지 않으니 실행이 안된다.
두 번째 template.json 파일 내용을 수정하도록 한다.

내용을 보고 자신에게 맞는 구성으로 수정해서 등록하도록 한다.
READMD.md 파일과 NEXTSTEPS.md 같은 파일들을 자신에 맞게 수정 후 저장한다.
자 일단 github에 올려보자. open 할 template이기 때문에 Repository 등록할 때 public으로 등록해야 한다.
git을 잘 사용하시는 사람은 git 명령어로 빠르게 처리해도 좋고 좀 미숙한 사람들은 VSCode에 있는 기능을 이용해 자신의 Repository에 등록하면 된다.

Initialize Repository 버튼을 클릭하면 현재 Project에 git init 가 실행되어 git 적용 환경이 된다.

상단 Message 부분에 "Initialize Template" 를 입력하고 Commit 버튼의 오른쪽 화살표를 클릭해 Commit & Sync 항목을 클릭한다.

3개 파일이 저장이 안 되었다고 저장 후 Commit을 진행한다고 한다. 각자 상황에 따라 메시지가 다를 테니 적절히 대처한다.

public repository를 선택한다.

저장소가 없던 게 생겼다. 그렇지만 클릭해 보면 정상적으로 소스가 upload가 안되었을 것이다.
다시 한번 Message 부분에 "initialize template" 등록 후 commit & Sync 버튼을 클릭해보자


여차저차 해서 정상적으로 개인 github에 등록이 되었다.
github에 관한 내용은 음.. VSCode로던지 직접 git 명령어로 등록하던지 별도로 학습해서 적용해보도록 하자. VSCode로 적용하는 게 생각보다 쉬우니 적용해보자.
이제 Template가 github에 등록이 되었으니 wails 의 -t 옵션으로 정상적으로 프로젝트를 생성할 수 있는지 확인해 본다.
공식적인 template가 아니므로 github 저장소 주소를 가져와서 프로젝트를 생성해야 한다.

주소를 복사해둔다.

wails init -n graduateapp -t https://github.com/dofstar/wails-vite-sveltekit-ts-tailwind-template.git

프로젝트가 정상적으로 생성되었다. 파일명도 tmpl 명칭이 빠졌고 설정값들도 정상적으로 적용되어 있다.
이제 구동이 되는지 확인해보자.
VSCode에서 Ctrl + ` 를 이용하면 TERMINAL을 사용해 CMD 를 사용하는 것과 같은 기능을 사용할 수 있다.

wails build

실행하면 wails build 부터 frontend 부분 빌드 및 번들링까지 해서 exe파일을 생성해준다.
그렇지만 지금은 exe파일을 실행하는 게 목적이 아니라 build를 통해 build, frontend/dist, frontend/node_modules 등등의 생성 폴더/파일이 필요해서이다. dist 폴더와 내부 파일들이 없으면 wails가 정상적으로 구동이 안된다. 이유는 main.go 에 설정되어있는 assets 부분 때문인데 추후 내용을 설명하기로 하겠다.
일단 build가 오류 없이 정상적으로 진행되어 종료되었다.
그럼 TERMINAL 창에 다음 명령어를 실행해보자.

wails dev

필요한 부분 빌드가 다시 진행되고 wails 프로그램이 실행되면서 sveltekit 내용이 wails에서 구동되면 완료이다!!!
빌드 후 실행이 안된다..
뭐가 문젠가..??
늦은 밤이라 내일 다시 확인해보기로 하겠다. 잘되는 것 같아 기분 좋았는데 갑자기 급우울이 오네.
=========================================================
문제점을 찾았다. wails.json 파일에

  "frontend:dev:watcher": "pnpm dev",
  "frontend:dev:serverUrl": "auto",

두 가지 항목을 추가해주자.
wails dev 실행하면 정상적으로 실행되는 걸 볼 수 있을 것이다.
기능들을 많이 넣다 보니 소스가 너무 복잡하다.
역시나 우려했던 대로 오픈소스 진영 프로젝트는 설정 지옥이 되는 게 어쩔 수가 없다.
설정을 한 곳으로 모으는 무언가가 있었으면 좋겠는데 좀 아쉽다.
마지막으로 tailwind를 사용하기 위한 설정은 적용되어 있는데 어떻게 적용해서 사용하는지 확인이 되지 않았다.
app.css 파일 추가 및 설정
layout.svelte 파일에 import 설정 추가
page.svelet 파일에 html 표현하기

3가지 적용에 대한 설명이 누락되었는데 너무 늦어서 일단 자야겠다.
사실 여기서 Template 만드는 방법은 종료를 해야겠다.
일단 시간이 없어서 개발을 우선순위로 둬야 하기 때문이다. 불필요한 파일 삭제는 마무리되었지만 설명 파일들 update도 해야 하고 잔잔하게 소스들 수정을 해야 완전히 끝나긴 하겠지만 지속적으로 조금씩 업데이트를 해나가는 방법으로 진행해야겠다.
자 그럼 이제부터 본격적으로 Graduate App을 만들어보도록 하자.
https://devguru.tistory.com/27?category=588709


[corepack 에 대한 설명]
https://luvstudy.tistory.com/188

corepack, pnpm, vite 사용해보기

corepack 소개 기본 개념 corepack은 node v16.9.0, v14.19.0부터 기본 포함된 실험적 기능으로 yarn, pnpm 같은 package manager를 프로젝트별로 지정하여 사용할 수 있게 한다. (yarn 개발자가 만들었다고 함.)..

luvstudy.tistory.com

728x90
728x90
반응형

github Repository를 만들고 회사에서 테스트겸 템플릿 다운을 받으려고 했더니 SSL 문제 떄문에 프로젝트 생성이 안되는 문제가 확인되었다. 보안적으로 많은 부분이 막혀있는 곳이라 좀 문제가 많은데 wails init 를 통해 템플릿 형태로 프로젝트 생성하는건 허용이되고 있다. 이게 어떤 이유로 되는지는 잘 모르겠다. 뭐 그부분까지 확인하려면 많은부분들을 확인해보고 알아봐야 하는데 템플릿을 만드는 주제와는 무관하므로 스킵하도록 하겠다. 그래서 왜 -t 옵션으로 프로젝트 생성을 못하는지 확인을 해보니 다음 설정파일이 누락되어 있어서 안되고 있는걸 확인했다.

template.json을 추가해서 내용을 등록했다.

{
    "name": "Wails + Vite + Sveltekit + Typescript + TailwindCSS template",
    "shortname": "wails-vite-sveltekit-ts-tailwind-template",
    "author": "dofstar(dofstar@gmail.com)",
    "description": "Wails + Vite + Sveltekit + Typescript + TailwindCSS template",
    "helpurl": "https://github.com/dofstar/wails-vite-sveltekit-ts-tailwind-template"
  }

파일을 추가 후 wails init 명령어로 프로젝트를 생성해보니 정상적으로 생성되었다.

이 template.json 설정파일의 내용은 추후에 어떤 용도로 사용될지 설명을 할텐데 일단 등록해두자.

자 템플릿 만들기 002 까지는 불필요한 파일들 싹 지우고 액기스 파일들만 남겨두고 순수 설정파일과 소스 파일들만 남겨뒀다.

Wails + Vite 설정만 되어있는 구조인데 이제 사용할 프레임워크들을 추가해보도록 하자.

frontend의 핵심 기술인 Sveltekit 과 Typescript 를 추가해보도록 하자.

frontend 폴더를 새롭게 교체하게 될텐데....

==============

헉.. wails 명령어에 template 기본을 생성해주는 기능이 있다.

그렇다면 제공된 기능을 최대한 사용하는 구조로 템플릿을 만들어봐야지!!

-- 끝

728x90
728x90
반응형

01. Wails 기본 프로젝트 생성


다른 모듈들이 추가되지 않은 순수 Wails 프로젝트를 생성하자.

wails init -n wails-vite-sveltekit-ts-tailwind-template

cd wails-*

git init

(tip) cd 명령어에서 아스트링크(*)를 사용하게되면 뒤의 단어들을 생략할 수 있다.
Project Templete 를 보면 vanilla 라고 되어 있는데 흔히들 바닐라JS 라고 하면 Javascript 기본을 바닐라JS라고 한다.
git init는 git이 설치되어 있다면 적용할 수 있다. 

현재 기준으로 wails 프로젝트를 생성하게되면 다음과 같은 폴더 구조를 가지게 된다.

폴더와 config 파일들을 자세하게 파보도록 하자.

최상위 폴더에는 다음 파일들이 있다.

.gitignore : git에서 소스관리를 하지 않을(ignore 할) 필터링 내용이 등록되어 있다.
wails.json : wails 에서 관리되는 설정
README.md : 프로젝트 설명 파일
go.mod : go 에서 사용할 dependency 정보 설정
go.sum : go 에서 사용하는 dependency 별 체크섬을 기록해두고 변조 여부를 검사하는데 사용
main.go : wails 실행 시 최초로 실행되는 main 함수가 포함되어 있는 source
app.go : main에서 호출 해서 사용할 사용자 정의 함수들이 포함되어 있는 source

기본적으로 Root에는 다음과 같이 source가 세팅된다.

기본으로 설정된 프로젝트에서 각종 config 파일들을 변경해줘야 하는데 먼저 wails.json 파일을 수정하도록 하자.

초기 설정은 npm을 사용하게 되어 있다. 하지만 성능적인 부분이나 디스크 공간을 효율적으로 사용하는 pnpm을 사용하도록 한다.

초기설정

{
  "name": "wails-vite-sveltekit-ts-tailwind-template",
  "outputfilename": "wails-vite-sveltekit-ts-tailwind-template",
  "frontend:install": "pnpm install",
  "frontend:build": "pnpm build",
  "frontend:dev:watcher": "pnpm dev",
  "frontend:dev:serverUrl": "auto",
  "author": {
    "name": "noname",
    "email": "noname@gmail.com"
  }
}

go.mod 파일을 열어보자

mod 파일의 상단 부분에 go version을 1.19 버전으로 수정하고 wails 버전을 가장 최근에 update된 v2.1.0 버전으로 수정하자.

go 1.19

require github.com/wailsapp/wails/v2 v2.1.0

그런데 wails 버전을 v2.1.0으로 수정하더라도 Wails CLI 버전이 자동으로 변경되지 않는다.

wails update

실행해서 Wails CLI 버전을 update 해준다.

이미 업데이트 된 상태에서 실행된거라 내용이 조금 다를 수 있다

이미 업데이트 된 상태에서 실행한거라 내용이 좀 다를 수 있지만 Latest Release 가 v2.1.0 버전이면 정상이니 넘어가도록 하자.

wails update 하게되면 require 부분의 버전들도 자동적으로 변경되는지 확인을 안해봤는데 크게 중요한거 아니니 궁금해서 미칠것 같다면 변경전 내용과 변경후 버전을 캡쳐해서 비교해봐도 좋다.

자.. 이렇게 root 부분의 수정할 파일들을 모두 살펴보았다.

다음은 wails의 frontend 부분 소스가 담겨있는 frontend 폴더를 살펴보자.

우선 가장 기본이 되는 설정이 있는 package.json 파일을 살펴보자

가장 기본적인 Wails 프로젝트를 생성했기 때문에 dependencies 에 설정된게 거의 없다.

Vite 가 기본적으로 설정되어 있다.

  • 빠른 번들링
  • HMR (Hot Module Replacement) 기능

https://vitejs-kr.github.io/guide/

 

Vite

Vite, 차세대 프런트엔드 개발 툴

vitejs-kr.github.io

이 템플릿은 가장 최신 버전으로만 설정될 것이므로 Vite 버전을 변경해주자.

VS Code 에서 devDependencies 에 등록되어 있는 참조모듈의 버전을 변경하는 방법은 다음과 같다.

이미 버전을 확실히 알고 있다면 직접 입력해도 된다. 그렇지만 최신 버전이 정확히 모를 경우

"Vite" : "^2.9.9"  에서 ": 부터 라인 끝까지 삭제 한후 ":"을 입력하고 Ctrl + Space를 누른다. 그럼 가장 최신버전을 표시해준다.

최신버전 로딩중
최신버전 선택

 

상단에 보면 Version이 있는데 이 버전은 개발할 프로젝트의 빌드 버전이다. 사용자가 버전업 할 사용자 버전을 등록하면 된다. 뭐 귀찮다면 변경하지 않아도 좋은데 가능하면 기능 업데이트때 마다 의미있는 버전을 적어주는게 좋다.

다시 frontend 폴더를 확인해보자

dist 폴더와 wailsjs 가 보일것이다.

이 폴더는 root 폴더에서 wails build 명령으로 새롭게 생성되므로 삭제 해도 무방하다.

최종적으로 설정이 마무리 될 시점이나 중간중간 wails build를 실행할 것이므로 일단 삭제하도록 하자.

index.html 파일과 src 폴더의 파일은 VanillaJS 기준 Source 파일들인데 typescript 용 Source는 어떻게 구성되는지 정확히 모르겠다. 일단 구동에 필요한 Source이므로 놔두도록 하자.

728x90
728x90
반응형

Rust - Tauri를 잠시 보류하고 Golang 기반의 Wails를 선택하고나니 좀 길이 보이기 시작했다.

Tauri가 막 엄청나게 어렵고 복잡하기보다는 시간적인 압박감때문에 계속 진도가 안나갔던것 같은데 심리적인 안정감 떄문인지 훨씬 진행속도가 빠르다는걸 느끼고 있다.

일단 거두절미하고...

Wails와 어떤기술을 사용 해야할지 검색을 좀 해보니 다음 기술들이 조합이 되어야 할 것 같았다.

Wails + Vite + Sveltekit + Typescript + Tailwind 조합이 딱 적당할 것 같았다.

각 기술들의 장단점들인 인터넷에 널려있으니 검색해도록 하자. 혹시 댓글로 요청이 많이 오면 별도로 정리하는 글을 적어보도록 하겠다.

Wails 개발홈페이지에서 제공하는 템플릿 기반으로 프로젝트를 생성하려다 보니 문제가 좀 있었다.

https://wails.io/docs/community/templates

 

Templates | Wails

This page serves as a list for community supported templates. Please submit a PR (click Edit this page at the bottom)

wails.io

원하는 기술에 딱 부합되는 템플릿이 없었다. 

wails-vite-svelte-tailwind-template 가 제일 적합한것 같았는데 typescript 가 빠져있었고 sveltekit이 적용이 안되었다. 게다가 관리를 안해서 그런지 프로젝트를 생성해서 구동을 해보면 정상적으로 실행이 안되고 각 config 들을 손을 좀 봐야 정상 작동을 하는 수준이있다.

그래서 해당 template를 fork 받아서 수정해서 쓰려고 했는데 svelte와 sveltekit 설정이 조금 상이해서 약간의 수정으로 처리하기가 좀 어려운감이 들었다. 물론 웹쪽에 아주 전문가 분들이라면 뚝딱뚝딱 수정해서 사용하기는 하겠지만....

그래서 진행하는 프로젝트는 wails-vite-svelte-tailwind-template를 수정해서 진행하기로 하고 template를 새롭게 구성해보겠다고 마음먹었다. template 만드는건 처음 해보는 시도이기도 하고 웹쪽의 각 기술들이 어떤식으로 조합해서 구성해야 하는지 기본부터 한번 해봐야 겠다는 생각도 들어서였다.

그래서 만드는 과정을 블로그에 기록하기로 하고 추후 다른 템플릿 만들때 참고 자료로 사용하면 좋을것 같아서 짬짬이 시간내서 진행하기로 한다. 

일단 https://github.com/dofstar/wails-vite-sveltekit-ts-tailwind-template 에 등록해서 작업해보도록 하겠다.

(지금 당장 생성된건 아니니 조금만 기다려달라..)

기술 조합은 다음과 같다.

Wails : 2.1.0
Vite : 3.18
SvelteKit : 1.0.0 (RC)
Typescript : 4.8.4
Tailwind : 3.1.8

 

 

728x90
728x90
반응형
728x90
728x90
반응형

https://www.codingworldnews.com/news/articleView.html?idxno=12914 

 

아파치, 웹 서버용 웹어셈블리 모듈 출시 - 코딩월드뉴스

VM웨어랩스(VMware Labs)가 웹어셈블리(WebAssembly) 바이너리를 실행하는 아파치의 웹 서버용 확장 모듈을 새로 출시하였다. mod_wasm 확장 모듈은 웹어셈블리로 컴파일된 애플리케이션에 대하여 아파치

www.codingworldnews.com

 

Rust를 학습하면서 자연스레 WASM 쪽으로 관심이 가게되었다. 

아직 초창기여서 이런저런 문제점들이 있기는한데 계속 성장하는 모습들이 앞으로의 세대교체를 짐작하게끔 한다. 하지만 꽤 많은 시간이 걸릴것이다.이미 주류가 되어버린 Javascript가 너무 탄탄한데 언젠간 Javascript가 가지고 있는 구조적인 문제나 성능문제가 대두되면서 웹브라우저가 WASM에 더 비중을 실어줄것이고 기능 확대를 점진적으로 진행할 것이다. 하지만 진입점이나 학습곡선이 Javascript보다는 높은편이라 단기간에 기대할 수 있지는 않을꺼라 생각한다.

 

mod_wasm은 두 개의 라이브러리로 구성이 되어 있다

1. mod_wasm.so로 아파치 C API 와 러스트 라이브러리 사이에 인터페이스를 제공하여 웹어셈블리 런타임을 관리할 수 있다. 즉 아파치 구성 옵션 및 러스트 라이브러리와의 연결을 담당한다.

2. libwasm_runtime.so 라이브러리는 아파치의 HTTP 요청을 받아 웹어셈블리 모듈을 구성하고 실행한다. 응답을 파싱한 이후 mod_wasm.so에 다시 관리 권한을 넘겨준다.

웹어셈블리는 바이너리로 컴파일되어서 스택 기반 가상 머신으로 웹 애플리케이션이 높은 성능을 발휘할 수 있도록 도와준다. 한번 적용해보고 싶긴하다. 조만간 성능테스트 결과 같은게 나오면 소개해보겠다.

728x90

+ Recent posts