TailwindCSS 설치 오류 해결

npm으로 tailwincss 설치시 “npm error could not determine executable to run”가 발생한다면 tailwindcss 버전이 맞지 않아서 그런 것이므로 낮은 버전을 설치하던가, package.json 파일을 수정한 다음 설치해야 합니다.

아래는 구체적인 에러 메시지 내용입니다.

TailwindCSS 설치 에러 화면

1. 원인

핵심 오류는 이 부분이에요 👇

Error: could not determine executable to run
verbose pkgid tailwindcss@4.1.14

npm 11 + tailwindcss 최신 버전(4.x) 조합에서 발생하는 공식적으로 보고된 버그입니다.
현재 tailwindcss@4.x는 정식 릴리스가 아니고, npx로 실행 시 **실행 파일(bin)**을 제대로 인식하지 못합니다.

2. 해결 방법

가. 안정 버전 설치(권장)

TailwindCSS 4.x 대신 3.x 버전을 사용하면 즉시 해결됩니다.

npm uninstall tailwindcss
npm install -D tailwindcss@3 postcss autoprefixer
npx tailwindcss init -p

이렇게 하면 정상적으로 실행됩니다.
→ tailwindcss@3.4.17 (2025년 기준 안정 버전) 기준으로 작동 확인됨.

나. 4.x를 유지하고 싶다면(실험적)

TailwindCSS 4.x는 구조가 바뀌어서 npx로 직접 실행이 안 됩니다.
대신 package.json의 “scripts”로 실행해야 해요.

1️⃣ package.json에 다음 추가:

"scripts": {
  "tailwind": "tailwindcss"
}

2️⃣ 그 다음 명령어:

npm run tailwind init -p

이 방식이면 Tailwind 4.x도 동작합니다.

&str와 lifetime

&str는 string literal과 빌림 문자열 두 가지가 있습니다. string literal은 String이 아니고, 큰 따옴표안에 들어가 있는 문자열이고, 빌림 문자열은 문자열을 빌리는 것입니다.

string literal은 static 수명이 있고, 빌림 문자열은 필요한 경우에 static이 아닌 수명을 지정해줘야 합니다.

1. 예시

가. string literal 예시

fn main() {
    let my_str = "I am a string";    
    println!("{}", my_str);
}

my_str 다음의 숨겨진 타입을 보면 &’static str로 되어 있습니다.

my_str의 타입이 &'static str임

println!(“{}”, my_str);을 println!(“{}”, &my_str);로 my_str 앞에 &를 붙여도 동작합니다.

나. &str 예시

fn print_str(my_str: &str) {
    println!("{my_str}");
}

fn main() {
    let my_str = "I am a string".to_string();
    print_str(&my_str);
}

my_str은 .to_string() 함수를 적용해서 string literal을 String 타입으로 변환했고, 이것을 빌리기 위해 my_str앞에 &를 붙여서 print_str의 인수로 전달했습니다.

2. 문자열 리터럴의 lifetime

fn returns_str() -> &str {
    "I am a str"
}

fn main() {
    let my_str = returns_str();
    println!("{my_str}");
}

위 코드는 returns_str 함수를 이용해 문자열을 리턴 받은 후 그 문자열을 화면에 출력하려고 하는 것인데,

명명된 lifetime parameter가 필요함

리턴 타입에서 명명된 라이프타임 파라미터가 기대된다는 에러 메시지가 나오면서 중간에 녹색으로 & 다음에 ‘static을 붙이는 것을 고려하라고 합니다.

반환 값이 “I am a str”, 다시 말해 문자열 리터럴이므로 &’static을 붙여야 합니다.

수정된 코드는 아래와 같습니다.

fn returns_str() -> &'static str {
    "I am a str"
}

fn main() {
    let my_str = returns_str();
    println!("{my_str}");
}

코드를 수정하고 실행하니 ‘I am a str’가 잘 출력됩니다.

'static을 추가해서 화면 출력이 잘 됨

위에 &str을 String으로 바꿔보라고 하는 제안도 있었는데, 이것을 적용하면 아래와 같이 됩니다.

fn returns_str() -> String {
    "I am a str".to_string()
}

fn main() {
    let my_str = returns_str();
    println!("{my_str}");
}

String 타입으로 바꾸면 수명 문제도 발생하지 않고, 출력값도 같습니다.

3. &str의 lifetime 1

#[derive(Debug)]
struct City {
    name: &str,
    date_founded: u32,
}

fn main() {
    let city_names = vec![String::from("Hoccimin"), String::from("Busan")];
    let my_city = City {
        name: &city_names[0],
        date_founded: 1952,
    };

    println!("{my_city:?}");
}

위 코드는 City 구조체를 만든 후 my_city라는 인스턴스를 만드는데, name으로 city_names라는 Vector에서 첫번째 것을 빌림으로 가져와서 대입하고, date_founded는 직접 1952를 입력한 다음 화면에 출력하는 것입니다.

그런데, 실행하면

빌림 문자열인 경우의 lifetime parameter 문제

&str에 명명된 lifetime parameter가 기대된다고 하면서

struct City 다음에 <‘a>를 붙이고, name 필드의 & 다음에도 ‘a를 붙이라고 합니다. 이렇게 하면 “name이 City 만큼 오래 살아야” 하므로 문제가 해결됩니다.

여기서 a는 라이프 타임 파라미터를 지칭하는 것으로 다른 문자를 사용해도 됩니다.

아래와 같이 수정하고 실행하면

#[derive(Debug)]
struct City<'a> {
    name: &'a str,
    date_founded: u32,
}

fn main() {
    let city_names = vec![String::from("Hoccimin"), String::from("Busan")];
    let my_city = City {
        name: &city_names[0],
        date_founded: 1952,
    };

    println!("{my_city:?}");
}

잘 출력됩니다.

lifetime parameter인 'a를 추가해서 화면 출력 성공

4. &str의 lifetime 2

그러나, &city_names[0]이 문자열 리터럴이 아니고 빌림 문자열이기 때문에 City 필드를 name: &’static str로 수정하면

#[derive(Debug)]
struct City {
    name: &'static str,
    date_founded: u32,
}

fn main() {
    let city_names = vec![String::from("Hoccimin"), String::from("Busan")];
    let my_city = City {
        name: &city_names[0],
        date_founded: 1952,
    };

    println!("{my_city:?}");
}

아래와 같은 에러가 발생합니다.

'a가 아닌 'static을 추가해서 에러가 발생

“&city_names[0]이라는 빌린 값이 충분히 오래 살지 못한다”고 하고,
그 아래에서는 “이 사용법은 city_names가 ‘static을 위해 빌리는 것이 요구된다”고 합니다. 왜냐하면 City 구조체 정의 시 name 타입 지정시’static 을 사용했기 때문입니다.

따라서, 3번에서와 같이 ‘a를 사용해야 합니다.

그런데 City 오른쪽에 <‘a>를 추가하지 않고, name 오른쪽에 만 ‘a를 추가하면

struct City {
name: &'a str,
date_founded: u32,
}
lifetime을 선언하지 않고 사용해 에러 발생

선언되지 않은 라이프타임 에러가 발생합니다. 따라서, City 오른쪽에 <‘a>를 추가해서 lifetime을 선언해야 합니다.

Iterator 트레이트의 연관 타입(associated type)

아래 코드의 핵심은 Iterator 트레이트의 연관 타입(associated type)트레이트 구현에 의한 타입 유추입니다. Item이 직접적으로 코드 안에서 보이지 않지만, 트레이트 시스템이 자동으로 Iterator trait의 type Item과 연결해 주고 있습니다.

1. 예제 코드

struct GivesOne;

impl Iterator for GivesOne {
    type Item = i32;
    fn next(&mut self) -> Option<i32> {
        Some(1)
    }

}

fn main() {
    let five_ones = GivesOne.into_iter().take(5).collect::<Vec<i32>>();

    println!("{five_ones:?}");
}

2. 구조체 정의

struct GivesOne;
  • GivesOne은 단순한 빈 구조체입니다.

3.Iterator 트레이트 구현

impl Iterator for GivesOne {
    type Item = i32;

    fn next(&mut self) -> Option<i32> {
        Some(1)
    }
}

여기서 중요한 부분이 바로 type Item = i32; 입니다.
Iterator 트레이트는 아래와 같이 정의되어 있습니다 (표준 라이브러리 축약본).

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

즉,

  • Item은 연관 타입으로, 이 이터레이터가 어떤 타입의 요소를 내보내는지를 지정합니다.
  • Self::Item이 Option<Self::Item> 안에서 쓰이죠.

    그런데 GivesOne에서는 type Item = i32;로 정했으니,
    → Self::Item이 i32가 됩니다.
    → 따라서 fn next의 실제 타입은 fn next(&mut self) -> Option<i32>가 되는 겁니다.

next()는다음 요소를 하나 반환하거나(None을 반환하면 종료) 하는 메서드입니다.
이터레이터는 내부 상태를 가지고 “이번에 무엇을 내보낼지”를 결정하죠.

GivesOne 구조체에는 아무 필드도 없습니다. 즉, 몇 번째 요소를 내보냈는지 추적하는 정보가 없습니다. 따라서, 이터레이터는 “항상 똑같은 값 1만 계속 내보내는” 무한 반복 이터레이터가 됩니다.

4. GivesOne.into_iter()의 동작

let five_ones = GivesOne.into_iter().take(5).collect::<Vec<i32>>();

여기서 헷갈릴 수 있는 부분은 into_iter()인데,
GivesOne이 스스로 Iterator를 구현하고 있기 때문에,
Rust는 GivesOne.into_iter()를 IntoIterator 트레이트를 통해 다음처럼 해석합니다.

impl<I: Iterator> IntoIterator for I {
    type Item = I::Item;
    type IntoIter = I;

    fn into_iter(self) -> I {
        self
    }
}

즉,

  • GivesOne이 이미 Iterator니까
    → IntoIterator도 자동으로 적용되고,
    → into_iter()는 그냥 자기 자신(GivesOne)을 반환합니다.

5. 체인 전체의 타입 흐름

  1. GivesOne.into_iter() → 타입: GivesOne
    위에서 알아본 바와 같이 next() 메소드는 영원히 Some(1)만 반환하고, None을 반환하지 않으므로 무한루프가 됩니다.
  2. .take(5) → 타입: Take<GivesOne>
    따라서.take(5)로 “앞의 5번만 가져오라”고 제한을 두는 것입니다.
  3. collect::<Vec<i32>>()
    collect()는 Item 타입을 기반으로 Vec<i32>를 만듭니다.

      6. 실제로 내부 타입이 어떻게 “보이지 않지만 작동하는가?”

      Rust는 트레이트 바인딩을 통해 자동으로 타입을 유추합니다.
      Item은 직접 변수로 존재하지 않고, 트레이트 정의 안에서 연관 타입으로만 쓰이죠.

      즉,
      GivesOne → Iterator → Item = i32
      이 연결이 트레이트 시스템 레벨에서 자동으로 이어지므로,
      어디에도 Item이라는 이름이 코드에 명시적으로 안 나와도,
      Rust는 Iterator<Item = i32>인 것을 정확히 압니다.

      7. 요약

      코드 부분역할
      type Item = i32;이터레이터가 내보내는 값의 타입을 지정
      fn next(&mut self)실제로 값을 하나씩 생성
      into_iter()Iterator를 자기 자신으로 반환
      take(5)최대 5번 next()호출
      collect::<Vec<i32>>()결과를 Vec<i32>로 수집

      결과적으로println!(“{five_ones:?}”)는 [1, 1, 1, 1, 1]을 출력하게 됩니다.

      Iterator 실행 결과

      Rust에서 ::의 용도 정리표

      ::은 “어디에 속한 항목인지” 또는 “어떤 타입인지”를 명시할 때 쓰이며, enum일 수도, 모듈일 수도, 제네릭 타입 지정(Turbofish)일 수도 있으며, 연관 함수, 상수 또는 연관 상수에 접근할 때도 사용되고, 트레이트 경로를 통한 메소드 호출 시에도 사용됩니다.

      1. Rust에서 ::의 용도 정리표

      구분예시설명관련 키워드
      모듈/경로 구분자std::fs::File모듈(std) → 하위 모듈(fs) → 항목(File)로 내려가는 경로 지정모듈(module), 네임스페이스(namespace)
      enum variant 접근Option::Some(5)Optionenum 안의 Somevariant에 접근enum
      연관 함수(associated function) 접근String::from(“hi”)타입(String)의 연관 함수(from) 호출struct, enum, trait
      상수 또는 연관 상수 접근std::f64::consts::PIf64타입의 연관 상수 PI에 접근상수(const)
      Turbofish (제네릭 타입 명시)“42”.parse::<i32>()제네릭 함수의 타입 매개변수를 명시적으로 지정제네릭(Generic)
      제네릭 타입 생성 시 타입 지정Vec::<i32>::new()제네릭 타입(Vec<T>)의 매개변수를 명시적으로 지정제네릭 타입
      트레이트 경로를 통한 메서드 호출<T as SomeTrait>::method()특정 트레이트에 정의된 메서드를 명시적으로 호출트레이트(impl 충돌 해결용)

      2. 예시 모음

      mod math {
          pub const PI: f64 = 3.14;
      }
      
      #[derive(Debug)]
      #[allow(dead_code)]
      enum Color {
          Red,
          Blue,
      }
      
      fn main() {
          // 1️⃣ 모듈 경로
          println!("{}", math::PI);        // -> 3.14
      
          // 2️⃣ enum variant 접근
          let c = Color::Red;
          println!("{c:?}");
      
          // 3️⃣ 연관 함수
          let s = String::from("hello");
          println!("{s:?}");
      
          // 4️⃣ Turbofish
          let n = "42".parse::<i32>().unwrap();
          println!("{n:?}");
      
          // 5️⃣ 제네릭 타입 지정
          let v = Vec::<i32>::new();
          println!("{v:?}");
      
          // 6️⃣ 트레이트 메서드 명시 호출
          use std::string::ToString;
          let x = 10;
          let s = <i32 as ToString>::to_string(&x);
          println!("{}", s);  // "10"
      }    

      위 코드의 실행 결과는 아래와 같습니다.

      ::의 다양한 용도 실행 결과

      BinaryHeap을 이용해 내림차순으로 정렬하기

      BinaryHeap은 가장 큰 값을 맨 앞에 위치시키고, 나머지 값들은 임의의 순서대로 저정하지만, pop()을 사용하면 내림차순으로 정렬한 것처럼 보이게 할 수 있습니다.

      1. 코드

      use std::collections::BinaryHeap;
      
      fn main() {
          let mut jobs = BinaryHeap::new();
      
          jobs.push((100, "Write back to email from the CEO"));
          jobs.push((80, "Finish the report today"));
          jobs.push((5, "Watch Some YouTube"));
          jobs.push((70, "Tell your team members thanks for always working hand"));
          jobs.push((30, "Plan who to hire next for the team"));
      
          while let Some(job) = jobs.pop() {
              println!("You need to: {}", job.1)
          }
      }

      위 코드를 실행하면 아래와 같이 중요한 일부터, 다시 말해 jobs의 첫번째 항목 점수를 기준으로 내림차순으로 정렬됩니다.

      2. BinaryHeap의 기본 비교 규칙

      BinaryHeap은 내부적으로 T: Ord 트레이트를 요구합니다.
      즉, 요소 T 간의 대소 비교(>, <)가 가능해야 하죠.

      Rust에서 tuple(예: (i32, &str))도 기본적으로 Ord를 구현하고 있으며, 사전식(lexicographical) 비교를 합니다.

      (a₁, b₁) < (a₂, b₂)
      ⟹ a₁ < a₂ 이면 그걸로 비교 종료,
      ⟹ a₁ == a₂ 이면 b₁과 b₂를 비교함.

      즉,
      (100, “CEO”)와 (80, “report”)를 비교할 때
      → 100 > 80이므로 (100, “CEO”)가 “더 큰” 값으로 간주됩니다.

      이 때문에 BinaryHeap은 자동으로 첫 번째 원소(i32) 를 기준으로 최대 힙을 구성합니다.

      3. “맨 위(root)”만 정렬되어 있는 이유

      BinaryHeap은 완전이진트리(Complete Binary Tree) 구조로 되어 있습니다.

      • 힙은 전체가 정렬되어 있지는 않습니다.
      • 단지 루트 노드(맨 위) 가 항상 가장 큰 값입니다.
      • 나머지는 “힙 속성(heap property)”만 유지합니다: 부모 ≥ 자식

      그래서 jobs.pop()을 반복할 때마다

      • 매번 가장 큰 (i32, &str) 튜플이 하나씩 빠집니다.
      • 내부적으로 남은 값들이 재배열되어 다시 최대 힙이 됩니다.

      결과적으로, pop()을 반복하면 내림차순 정렬된 순서로 출력되는 것처럼 보이는 거예요.

      4. 즉, “항상 정렬되어 있는 게 아니라”

      BinaryHeap의 내부 저장순서는 무작위입니다.
      하지만 pop()할 때마다 가장 큰 값이 나오는 이유는
      “루트만 최대값이 되도록 하는 BinaryHeap 속성” 때문입니다.

      5. 정리

      개념의미
      내부 저장 순서무작위 (정렬 아님)
      비교 기준Ord 구현(튜플이면 첫 번째 요소부터 비교)
      pop()동작항상 최대값 반환
      출력 결과내림차순처럼 보임 (사실은 매번 루트 제거 과정 때문)

      6. 오름차순 정렬

      만약 작은 값부터 꺼내고 싶다면, 즉 “최소 힙(min-heap)”을 원한다면 이렇게 하면 됩니다:

      use std::cmp::Reverse;
      use std::collections::BinaryHeap;
      
      fn main() {
          let mut jobs = BinaryHeap::new();
      
          jobs.push(Reverse((100, "CEO")));
          jobs.push(Reverse((80, "Report")));
          jobs.push(Reverse((5, "YouTube")));
      
          while let Some(Reverse(job)) = jobs.pop() {
              println!("You need to: {}", job.1);
          }
      }
      

      이렇게 하면 작은 i32부터 오름차순으로 출력됩니다.

      요약하자면 👇

      BinaryHeap은 내부가 정렬된 컬렉션이 아니라, 루트에만 최대값을 유지하는 자료구조이고,
      (i32, &str) 튜플은 기본적으로 첫 번째 값으로 비교되기 때문에,
      pop()을 반복하면 결과적으로 i32 기준 내림차순 출력처럼 보입니다.

      Workspace, Trait, Test and Run

      Setting up a Cargo workspace is the cleanest way to manage both your aggregator library and the aggregator_app binary in one place.


      Ⅰ. Setting Up a Cargo workspace

      1. Create a new workspace folder

      Make a parent folder (say aggregator_workspace):

      mkdir aggregator_workspace
      cd aggregator_workspace

      2. Create the workspace manifest

      Inside it, create a Cargo.toml file:

      [workspace]
      members = [
          "aggregator",
          "aggregator_app",
      ]

      This tells Cargo that both crates are part of one workspace.


      3. Add your crates

      Now create the two crates inside the workspace:

      cargo new aggregator --lib
      cargo new aggregator_app

      Now the folder structure looks like this:

      aggregator_workspace
      ├── Cargo.toml      # Workspace manifest
      ├── aggregator      # Library crate
      │   ├── Cargo.toml
      │   └── src/lib.rs
      └── aggregator_app  # Binary crate
          ├── Cargo.toml
          └── src/main.rs

      4. Define the library

      Edit aggregator/src/lib.rs:

      pub fn summarize(text: &str) -> String {
          format!("Summary: {}", text)
      }

      5. Link the binary to the library

      In aggregator_app/Cargo.toml, add the dependency:

      [dependencies]
      aggregator = { path = "../aggregator" }

      6. Use it in the binary

      Edit aggregator_app/src/main.rs:

      use aggregator::summarize;
      
      fn main() {
          let text = "Rust makes systems programming fun and safe!";
          let summary = summarize(text);
          println!("{}", summary);
      }

      7. Build and run

      From the workspace root:

      cargo run -p aggregator_app

      Output:

      Summary: Rust makes systems programming fun and safe!

      ✅ Now you have:

      • aggregator → library crate
      • aggregator_app → binary crate
      • managed together in one workspace.

      Ⅱ. How to Test the Library

      Let’s extend the workspace so you can test your aggregator library while also running the app. There are two ways to test library, one is adding tests inside the library and the other is adding separate integration tests in a tests/ directory


      1. Add tests inside the library

      Edit aggregator/src/lib.rs:

      /// Summarize a given text by prefixing it.
      pub fn summarize(text: &str) -> String {
          format!("Summary: {}", text)
      }
      
      // Unit tests go in a special `tests` module.
      #[cfg(test)]
      mod tests {
          // Bring outer functions into scope
          use super::*;
      
          #[test]
          fn test_summarize_simple() {
              let text = "Hello Rust";
              let result = summarize(text);
              assert_eq!(result, "Summary: Hello Rust");
          }
      
          #[test]
          fn test_summarize_empty() {
              let text = "";
              let result = summarize(text);
              assert_eq!(result, "Summary: ");
          }
      }

      2. Run the tests

      From the workspace root:

      cargo test -p aggregator

      Output (example):

      running 2 tests
      test tests::test_summarize_simple ... ok
      test tests::test_summarize_empty ... ok
      
      test result: ok. 2 passed; 0 failed

      3. Run the app

      From the same root:

      cargo run -p aggregator_app

      Output:

      Summary: Rust makes systems programming fun and safe!

      4. (Optional) Integration tests

      You can also add separate integration tests in a tests/ directory inside the aggregator crate:

      aggregator/
      ├── Cargo.toml
      ├── src/lib.rs
      └── tests
          └── integration_test.rs

      tests/integration_test.rs:

      use aggregator::summarize;
      
      #[test]
      fn integration_summary() {
          let text = "Integration testing is easy!";
          let result = summarize(text);
          assert_eq!(result, "Summary: Integration testing is easy!");
      }

      Run all tests in the workspace:

      cargo test

      ✅ Now you have:

      • Unit tests inside src/lib.rs
      • Integration tests in tests/
      • Ability to run tests and app from the same workspace.

      Mut와 Shadowing

      Mut는 “변수의 값을 바꿀 수 있다(mutable)”는 의미이고, Shadowing은 “같은 변수명을 사용해서 이전의 변수를 가리는” 기능입니다. Shadowing을 통해 Mut의 기능을 대체할 수도 있고, 둘 다 사용하지 않는다면 변수명을 여러 개 사용해야 하는 번거로움이 있습니다.

      1. Mut를 사용한 경우

      아래 예시는 x를 mutable(가변) 변수로 선언한 다음
      => let mut x = 9;

      x의 값을 바꿔가면서 원하는 값인 final_number를 출력하는 코드입니다.
      => x = times_two(x);
      => x = x + y;
      이 때 반환값은 x라고 ;없이 씁니다.

      fn times_two(number: i32) -> i32 {
          number * 2
      }
      fn main() {
          let final_number = {
              let y = 10;
              let mut x = 9;
              x = times_two(x);
              x = x + y;
              x
          };
          println!("The number is now:{}", final_number);
      }

      그러나, let final_number가 끝나는 지점에는 let 문이므로 ;을 반드시 붙여야 합니다. 안 붙이면 아래와 같이 ;을 기대했는데, println이 발견됐다는 에러가 표시됩니다.

      let문은 ;으로 끝나야 하는데, ;이 없어 에러가 남

      Rust의 특이한 점이 불변(immutable)이 변수의 기본 상태이고, 변수의 값을 바꿀 수 있게 하려면 mut를 붙여야 한다는 점입니다.

      ※ 함수 구문

      fn times_two(number: i32) -> i32 {
          number * 2
      }

      Rust에서 함수를 선언할 때는 fn을 사용하고 그 다음에 함수명을 붙이는 것은 다른 언어와 같습니다.

      그 다음 괄호 안에 인수명을 입력하는데, 여기서는 number이고,

      반드시 인수의 타입을 입력해야 하는데, : 다음에 i32 식으로 “32비트 부호 있는 정수” 타입이라고 명시합니다.

      이것이 변수의 경우 추론(inference)이 돼서 타입을 반드시 기재해야 할 필요가 없는 것과 다른 점입니다. 변수의 경우 정수는 i32, 실수는 f64가 기본형입니다.

      그리고, 반환 값이 있으면 반환 값의 형식도 입력해야 하는데,
      -> 다음에 i32식으로 입력합니다.

      따라서, 반환값이 없으면 ‘-> 반환 값 형식’을 입력하지 않습니다.

      그리고, 중괄호 안에 함수의 내용을 기록하고, 반환값을 마지막에 입력하는데, 여기서는 반환값이 number * 2로 인수에 2를 곱한 값을 반환하는 것인데, 마지막에 ;을 붙이지 않는 것도 중요한 점의 하나입니다.

      2. Shadowing을 사용한 경우

      Shadowing은 “그림자처럼 따라 다님”이란 의미이므로, 여기서는 원래 있던 x 변수를 가리는 역할을 합니다.

      Shadowing을 이용하게 되면 x = 을 let x =이라고 쓰고,
      처음 변수 선언할 때 mut 없이 let x = 9;이라고 씁니다.

      fn times_two(number: i32) -> i32 {
          number * 2
      }
      fn main() {
          let final_number = {
              let y = 10;
              let x = 9;
              let x = times_two(x);
              let x = x + y;
              x
          };
          println!("The number is now:{}", final_number);
      }

      만약 let mut x = 9;라고 mut를 붙여도 결과는 같지만,
      ‘변수가 mutable일 필요가 없다”는 경고와
      mut를 제거하라는 도움말이 표시됩니다.

      가변 변수가 아닌데, mut가 붙어 있어서 경고 메시지가 표시되고, mut를 제거하라는 도우말이 표시됨

      3. shadowing과 mut를 사용하지 않은 경우

      fn times_two(number: i32) -> i32 {
          number * 2
      }
      fn main() {
          let final_number = {
              let y = 10;
              let x = 9;
              let twice_x = times_two(x);
              let twice_x_and_y = twice_x + y;
              twice_x_and_y
          };
          println!("The number is now:{}", final_number);
      }

      1과 2 예제에서는 변수명으로 x 하나만을 사용했는데,

      이번 예제 3에서는 x, twice_x, twice_x_and_y라는 변수 3개를 사용해야 해서 매우 번거롭습니다.

      Rust에서 사용되는 기호의 의미 및 읽는 방법

      Rust는 소유권, 빌림, 트레이트 등 문법도 복잡하지만, 기호도 처음 보는 것이 많아 헷갈리게 하는데 정리해봤습니다. 인쇄해놓고 보면 좋을 듯 합니다.

      기호 / 표기의미설명예시영어 읽기한국어 읽기
      !매크로이름 뒤에 붙어 해당 이름이 매크로임을 표시println!, dbg!, vec!macro / bang매크로
      ?에러 전파Result나 Option에서 에러를 즉시 반환let x = foo()?;question mark물음표
      ::경로 연산자모듈, 구조체, 함수 등의 경로 지정std::io::Resultdouble colon더블 콜론 / 경로 연산자
      *역참조참조 값을 직접 접근*r = 10;asterisk / deref별 / 역참조
      _와일드카드사용하지 않는 값, 이름 무시let _ = foo();underscore언더스코어 / 무시
      ->함수 반환 타입함수 반환을 표시fn foo() -> i32 { 5 }arrow화살표 / 반환
      =>매칭 암시match에서 패턴 결과 지정match x { 1 => “one”, _ => “other” }fat arrow두꺼운 화살표 / →
      ::<turbofish제네릭 타입을 명시적으로 지정Vec::<i32>::new()turbofishturbofish / 제네릭 타입 명시
      ..범위 연산자일부 범위, 구조체 업데이트 등에 사용0..5, ..Default::default()dot dot범위 / 나머지 필드
      ..=포함 범위끝 값을 포함하는 범위0..=5dot dot equal포함 범위
      &참조변수/값을 빌려오기let r = &x;reference참조
      &mut가변 참조변수/값을 가변으로 빌려오기let mr = &mut y;mutable reference가변 참조
      ref/ ref mut패턴용 참조패턴 매칭에서 참조를 얻음let Point { x: ref r, .. } = p;ref / ref mut참조 / 가변 참조 패턴
      mut가변성변수 또는 참조를 변경 가능하게let mut x = 5;mut뮤트 / 가변
      ..(패턴)구조체/튜플 패턴일부 필드만 바인딩하고 나머지 무시Point { x, .. }dot dot범위 / 나머지 필드 무시
      라이프타임참조가 유효한 범위를 지정&’static strsingle quote / tick라이프타임 / 스태틱 라이프타임

      ChatGPT의 도움을 받았는데, ChatGPT도 헷갈려 하네요.
      예를 들어 *를 asterisk라고 하고, !를 bang이라고 하고 헷갈리네요.

      하나 더 참고

      'Underscore'와 'underbar'는 같은 기호(_)를 가리키는 다른 이름으로, 'underscore'가 정식 명칭이고 'underbar'는 비정식적인 표현입니다.

      Git: Rust 도입 및 빌드 시스템에서 필수화 발표

      GeekNes에 올라와 있는 12시간전 소식인데, Git 프로젝트에서 Rust를 코어에 도입하고, Git 3.0부터는 Rust가 빌드 필수 요건이 될 것임을 공식 발표했다고 합니다.

      아래는 자세한 내용입니다.

      • Git 프로젝트는 앞으로 Rust를 코어에 도입하고, Git 3.0부터는 Rust가 빌드 필수 요건이 될 것임을 공식 발표
      • 이번 패치 시리즈는 과거 C99 기능 도입처럼 시험적 도입(test balloon) 성격으로 진행되며, Rust 도입 인프라를 점진적으로 마련하는 목적
      • 첫 단계로 의존성이 거의 없는 varint.c 모듈을 Rust로 변환하여 C-Rust 상호운용성과 빌드 툴링을 검증함
      • 현재는 Meson 빌드만 지원하며, 향후 Makefile 지원과 CI에서 Rust 빌드 검증 및 cargo format 기반 포맷 일관성 검사를 추가할 예정
      • 이는 Git 커뮤니티와 배포자들에게 새로운 Rust 툴체인 요구사항에 적응할 시간을 주면서, 장기적으로 코드 안전성과 확장성을 높이는 중요한 변화임

      1. Rust 도입 배경

      • Git은 안정성과 유지보수를 위해 언어적 진화를 검토해왔음
      • Rust 도입은 메모리 안전성 강화현대적 툴체인 활용성능 최적화 가능성 확보라는 의미가 있음
      • 아울러 현대적인 언어 도입을 통해 보다 견고한 코드베이스를 구축하고자 함
      • Git 3.0 릴리스 시점에는 Rust가 반드시 필요해짐을 사전 공지하여 생태계 준비 시간을 확보하려는 목적
      • 단계적으로 Rust 코드 적용 범위를 확대하며, 궁극적으로 일부 핵심 기능도 Rust로 재구현할 방침

      2. 시험적 패치 시리즈

      • Rust 첫 적용 모듈로 varint.c를 선택
        • 매우 단순하고 의존성이 없음
        • C ↔ Rust interop 검증 가능
        • Git 전체 기능에 영향 없이 실험 가능
      • 모든 테스트는 varint.rs 버전에서 통과함

      3. 빌드 시스템 변화

      • 현재는 Meson 빌드 시스템에서만 Rust 지원 추가
      • 향후 Makefile에도 Rust 지원을 추가할 계획임
      • CI 관련 작업도 준비 필요
        • Rust 빌드 및 동작 검증
        • cargo format을 통한 코드 스타일 일관성 확보
        • 기타 툴링 및 유지보수 자동화

      4. 향후 계획

      • 이번 작업은 Rust 기능 자체보다 도입 프로세스 실험에 초점이 있음
      • 이후에는 xdiff 모듈을 포함해 더 많은 Git 내부 기능을 Rust로 재작성할 수 있음
      • Rust를 점진적으로 확장 적용하면서 생태계 전체가 Rust 기반 빌드 환경에 적응하도록 유도할 예정

      5. 시사점

      • Git은 역사상 가장 중요한 언어적 전환을 준비 중임
      • Rust 필수화를 통해 안전성·유지보수성·장기적 발전 가능성을 확보할 수 있음
      • 배포자 및 개발자들은 향후 Git 생태계에서 Rust 개발 환경 구축이 필수가 될 것임

      그동안 Rust의 장점에 대한 내용은 많았는데, 보급이 잘 안돼서 의구심이 있었는데, 이런 소식을 듣게되니 힘이 납니다.

      이 소식을 포함해서 댓글은 아래 링크를 통해 확인 바랍니다.

      https://news.hada.io/topic?id=23190

      ‘Copy Anything to Clipboard’ WordPress Plugin

      블로그나 티스토리를 쓸 때는 당연히 코드 복사 기능이 있어 불편하지 않았는데, WordPress로 바꾸니 복사 아이콘도 없고, Ctrl + A키를 누르면 화면 전체가 선택돼서, 찾아보니 Copy Anything to Clipboard(한글명 클립보드에 복사)란 플러그인이 있습니다.

      1. Copy Anything to Clipboard

      가. 플러그인 추가

      알림판에서 플러그인 > 플러그인 추가를 클릭하고

      플러그인 추가 메뉴

      플러그인 검색 란에 ‘copy anything’이라고 입력하면 한글로 ‘클립보드에 무엇이든 복사하기’로 표시되고, 왼쪽 아이콘 안에 ‘Copy Anything to Clipboard’가 표시됩니다.

      '클립보드에 무엇이든 복사하기(Copy Anything to Clipboard)' 플러그인

      지금은 오른쪽 버튼명이 설치가 되고 활성화돼서 ‘활성’이라고 되어 있는데, 설치가 안되어 있다면 오른쪽 플러그인 처럼 ‘지금 설치’라고 되어 있을 것이므로 ‘지금 설치’ 버튼을 눌러 설치합니다.

      나. 설정 – 실패 => ‘다. 설정 – 성공’으로 건너뛰어도 됩니다.

      플러그인 설치를 하면 아래와 같이 설정 화면이 나옵니다.

      '클립보드에 복사' 설정 메뉴

      처음에는 뭔지 모르고 그냥 글쓰기로 넘어가서 글을 작성하고,

      왼쪽 위 블록 삽입기를 누르고, 맨 아래로 내려가서 ‘클립보드에 무엇이든 복사하기’ 그룹의 ‘복사 아이콘’을 눌러 코드 위에 ‘복사 아이콘’을 추가한 후

      '블록 삽입기'의 '클립보드에 무엇이든 복사하기 그룹'내 '복사 아이콘'

      보기 > 새 탭에서 미리 보기를 눌러

      새 탭에서 미리보기

      글 위의 복사 아이콘을 누르면

      글에 '복사하기 아이콘'이 추가된 모습

      ‘Copied’라고는 표시되는데, 메모장에 붙여 넣기를 하면

      '복사 아이콘' 클릭시 'Copied'란 설명이 표시됨

      아무 것도 붙여지지 않습니다.

      다. 설정 – 성공

      (1) 설정

      ‘클립보드에 무엇이든 복사하기’ 플러그인을 설정하기 위해

      알림판 > 설정 > ‘클립보드에 복사’를 누른 다음

      설정 > '클립보드에 복사' 메뉴

      설정을 하는데, 이미 설정이 되어 있으므로, ‘code 복사’ 아래의 편집을 눌러보겠습니다.

      '클립보드에 복사' 설정 목록

      그러면 아래와 설정된 내용이 나오는데,

      '클립보드에 복사' 설정 사항(Post Title, Selector, Format, Style 등)

      Post Title은 ‘code 복사'(각자 알맞게 설정)이고,

      설정(Settings)의 의미는 pre 태그(Selector)가 있으면

      Default 형식(글 복사)으로 복사하는데,

      모양은 아이콘이 아닌 버튼 모양이고,

      버튼의 설명문은 Copy to Clipboard, Copied 등입니다.

      설정을 다 했으면 아래 Save Changes 버튼을 눌러 저장합니다. 위에서는 Save Changes가 비활성화되어 있었는데, 설정을 바꾸면 활성화됩니다.

      '클립보드에 복사' 저장 버튼

      (2) Style – 아이콘과 버튼

      버튼 모양이 아닌 아이콘 모양으로 하면

      코드 블록의 배경색이 회색인 경우 인지가 잘 안되어

      '복사 아이콘'이 회색 배경인 경우 잘 구분되지 않음

      버튼 모양으로 하는 것이 구분이 잘됩니다.

      '복사 버튼'은 회색 배경인 경우에도 잘 보림

      (3) Format – Default, Google Docs, Email

      참고로 Format은 Default, Google Docs, Email 형식이 있습니다.

      '클립보드에 복사' 설정 중 Format 옵션

      (4) 적용 결과

      이제 작성된 글을 보면 코드뿐만 아니라 정형화된 형식으로 작성된 부분에도 ‘Copy To Clipboard’가 윗 부분에 추가된 것을 알 수 있고,

      Code와 정형화된 형식에 '클립보드에 복사' 설정이 적용된 화면

      이미 작성된 글 전체에 일괄적으로 적용된 것을 알 수 있습니다.

      3. 상세한 설명 사이트

      코드 말고, 다른 설정을 하고 싶으면 아래 사이트의 ‘continue reading’를 눌러서 읽어보기 바랍니다.

      코드 복사뿐만 아니라 아래와 같이 다양한 복사 기능을 제공합니다.

      '클립보드에 복사'가 적용 가능한 다양한 종류

      한번씩 읽어보면 좋을텐데 읽어도 지식이 짧아 여러 번 읽어봐야 하겠습니다.