<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기록하는삶</title>
    <link>https://mingchin.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 12 Aug 2026 16:54:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>mingchin</managingEditor>
    <item>
      <title>[논문 리뷰] Qwen3-ASR</title>
      <link>https://mingchin.tistory.com/507</link>
      <description>&lt;h3 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size23&quot;&gt;1. 한 줄 핵심 요약&lt;/h3&gt;
&lt;blockquote data-path-to-node=&quot;4&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;4,0&quot; data-ke-size=&quot;size16&quot;&gt;52개 언어를 지원하며 단일 아키텍처로 스트리밍과 오프라인 추론을 모두 완벽하게 소화하는 고효율 오픈소스 음성 인식(STT) 및 강제 정렬(Forced Alignment) 모델. STT는 1.7B와 0.6B 모델, Forced aligner는 0.6B 모델 공개.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;2. 연구 배경 및 문제 정의&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;상용 API와의 성능 격차:&lt;/b&gt; 기존 오픈소스 ASR(자동 음성 인식) 모델들은 강력한 상용 서비스(API)에 비해 다국어 처리나 복잡한 환경(노이즈, 노래 등)에서의 인식률이 부족&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;스트리밍/오프라인 아키텍처의 파편화:&lt;/b&gt; 실시간(Streaming) 서비스와 대용량 오프라인 처리를 위해 보통 구조가 다른 별개의 모델을 운용해야 하는 번거로움이 존재&amp;nbsp; &amp;lt;&amp;lt; 매우 공감하는 부분&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;타임스탬프 추출의 한계:&lt;/b&gt; 정밀한 텍스트-음성 동기화에 필수적인 '강제 정렬(Forced Alignment)' 모델 중, 다국어를 빠르고 정확하게 지원하는 통합 솔루션이 부재&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;3. Contributions&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;SOTA급 다국어 모델 공개:&lt;/b&gt; 52개 언어와 방언을 지원하며 Whisper large-v3를 능가하는 1.7B 모델과, 효율성에 극대화된 0.6B 모델을 Apache 2.0 라이선스로 오픈소스화.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;스트리밍/오프라인 통합:&lt;/b&gt; 동적 윈도우 크기를 조절하는 메커니즘을 통해 하나의 모델로 짧은 실시간 청크부터 최대 20분에 달하는 긴 오디오까지 유연하게 처리.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;NAR 기반 강제 정렬 모델 제안:&lt;/b&gt; 기존의 Auto-regressive 방식이 아닌 Non-Autoregressive(NAR) 방식으로 빠르고 정확하게 단어/문장 단위 타임스탬프를 예측하는 Qwen3-ForcedAligner-0.6B 최초 공개 &amp;rarr; forced aligner 쪽은 대충 봤는데, 기존의 Montreal Forced Aligner를 사용해 pseudo-labeling하고, 몇 가지 학습 기법을 적용해 정확도를 끌어 올렸다고 한다. &lt;s&gt;사실 근데 이건 별 기대 안되고.. 커뮤니티 반응도 좀 봤었는데 그냥 그렇다는 글을 몇 개 봤었다. 하지만 그 필요성과 잘 연구 안해주는 분야인데 공개하준 것에 감사하다.&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;4. 방법론 / 아키텍처&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;AuT (Audio Transformer) 인코더 (~300M):&lt;/b&gt; 128차원의 Fbank(Mel-spectrogram 기반) 특징을 입력받아 8배 다운샘플링하여 12.5Hz 주기로 표현 공간을 압축한다. 핵심은 &lt;b data-index-in-node=&quot;118&quot; data-path-to-node=&quot;10,0,0&quot;&gt;동적 윈도우(1~8초)가 적용된 FlashAttention&lt;/b&gt;으로, 이를 통해 컨텍스트 길이에 구애받지 않고 유연하게 작동한다. &amp;rarr; ASR로 사전 학습된 인코더를 가져온다. 무려 40million.. / next kaldi인 K2 쪽에서도 관련 기법을 보았었다. 실시간 모델을 따로 두지 않고 통합하는 시도가 맞는 방향이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;Qwen3-Omni 기반 백본:&lt;/b&gt; Qwen3-Omni 사전 학습 모델을 기반으로 하여 풍부한 언어 이해 능력을 ASR 태스크에 그대로 이식했다. 이미지, 오디오, 텍스트 input을 받을 수 있는 멀티모달 기반으로 3 trillion 토큰을 사전학습을 한 뒤, ASR에 맞게 finetuning 되었다고 한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt; ASR supervised finetuning: (&lt;/b&gt;Omni 기반이다보니) Instruction-following)을 억제하는 스타일 전이(Style Transfer) 학습, 묵음에 강인하도록 별도 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VnBFk/dJMcaiCvJJd/SXHmCE2ajDFEoO4E7U1mN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VnBFk/dJMcaiCvJJd/SXHmCE2ajDFEoO4E7U1mN1/img.png&quot; data-alt=&quot;묵음 별도 학습은 정말 필요하다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VnBFk/dJMcaiCvJJd/SXHmCE2ajDFEoO4E7U1mN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVnBFk%2FdJMcaiCvJJd%2FSXHmCE2ajDFEoO4E7U1mN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;772&quot; height=&quot;190&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;묵음 별도 학습은 정말 필요하다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;10&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;10,2,0&quot; data-index-in-node=&quot;0&quot;&gt; Reinforcement Learning: &lt;/b&gt;GSPO(Group Sequence Policy Optimization)를 적용했다고 하는데, 이는 따로 읽어봐야 할 것 같다. 쨌든 강화학습을 통해 잡음에 대한 강인성, 방언 등 어려운 발음에 대한 인식률을 끌어올렸다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SjdWE/dJMcaaEwuDU/d5HlF2CiYLcZlpmprUXUpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SjdWE/dJMcaaEwuDU/d5HlF2CiYLcZlpmprUXUpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SjdWE/dJMcaaEwuDU/d5HlF2CiYLcZlpmprUXUpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSjdWE%2FdJMcaaEwuDU%2Fd5HlF2CiYLcZlpmprUXUpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;871&quot; height=&quot;373&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;5. 실험 설계 및 결과&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;평가 지표:&lt;/b&gt; 내부 및 다수의 오픈 벤치마크 데이터셋 사용, TTFT(Time-to-First-Token), Throughput(처리량), Alignment Shift(정렬 오차).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;실험 결과:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,1,0,0&quot;&gt;1.7B 모델:&lt;/b&gt; 오픈소스 최고 수준의 정확도(SOTA) 달성 및 주요 상용 API와 대등한 성능 &amp;rarr; 한국어 체감 성능 매우 훌륭&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,1,1,0&quot;&gt;0.6B 모델 (효율성):&lt;/b&gt; 평균 TTFT 92ms 달성. 동시 요청(Concurrency) 128 기준, &lt;b data-index-in-node=&quot;59&quot; data-path-to-node=&quot;12,1,1,1,0&quot;&gt;1초의 연산 시간으로 2,000초 분량의 음성을 전사&lt;/b&gt;하는 놀라운 처리량을 입증&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,1,2,0&quot;&gt;강제 정렬 모델:&lt;/b&gt; 기존 강제 정렬 모델들 대비 타임스탬프 오차율(Shift)을 67~77% 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;861&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tuhym/dJMcabKcS2s/p4Xn4kDGMcctZw4otOTyv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tuhym/dJMcabKcS2s/p4Xn4kDGMcctZw4otOTyv0/img.png&quot; data-alt=&quot;미쳐버린 수치다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tuhym/dJMcabKcS2s/p4Xn4kDGMcctZw4otOTyv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTuhym%2FdJMcabKcS2s%2Fp4Xn4kDGMcctZw4otOTyv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;861&quot; height=&quot;422&quot; data-origin-width=&quot;861&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;미쳐버린 수치다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;6. 후기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;12&quot;&gt;
&lt;li&gt;&lt;span&gt;whisper야 안녕:&lt;/span&gt;&lt;span&gt; 실시간과 배치를 모두 고려한 모델을 만들었다는 점, LLM 백본을 사용해 문맥에 강점이 있는 것, 동시처리면에서 압도적인 성능을 보이고, vllm으로 추론이 가능하다는 점, large-v3가 뭔가 맛이 가고.. finetuning 시에 오히려 성능이 떨어지고 할루시네이션이 심해진다는 점(나만의 경험이 아니라 다수의 의견임을 최근에 확인했다) 등을 고려할 때, whisper의 시대가 가버린 것 같다. 실제 보유한 벤치마크 성능도, 체감 성능도 whisper보다 좋았다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;직접 finetuning도 해보고 추론해본 결과, 할루시네이션에 특별히 강점이 있는지는 잘 모르겠다. whisper 대비 나은 것은 맞지만, 잡음이나 babble 등에 약한 것은 어쩔 수가 없다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;공개된 것처럼 vllm 기반 추론 + 동시처리 성능은 압도적이다. 다만 내가 구현할 때 기준으로 실시간 알고리즘은 공개했다고는 하지만 그냥 앞에서부터 누적하면서 반복 추론하는 불쉿코드가 공개된 상태였기 때문에, 직접 실시간에 가까운 windowing 추론을 구현해야했다. RTF도 정말 말도 안되는 수준이라, 웬만한 배치 추론은 벌써? 하는 시점에 결과가 나와있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;TTFT 직접 측정해봐도 120ms 수준이었다. (1.7B 모델) 부하를 걸면 점진적으로 증가하긴 하지만, 매우 안정적인 편이다. 특정 지점까지는 거의 변화가 없었다.&lt;/li&gt;
&lt;li&gt;cross-lingual / code switching도 꽤 잘 되는 편이다. finetuning 하면 이 부분이 희석될 수 있다고 생각하는데, 어떻게 해야 성능을 유지 또는 보완할 수 있을지 고민이 필요하다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;스케일링이나 학습 기법의 규모가 너무 압도적이라.. 사실 뭐 우리나라가 따라갈 재간이 있나 싶다. 사용된 데이터의 양도 그렇고, 멀티모달리티 기반 백본 학습 후 finetuning, 거기에 강화학습까지.. 한국어 전용으로 학습한다고 해도, 여기에 finetuning 하는게 훨씬 나아보인다. 공개된 학습 데이터들의 전사 규칙이 일관되지 못한 것이 문제가 되는데, 적당히 큰 LLM에 프롬프트 잘 만들어서 ITN 시키면 어느 정도 학습에 용이한 형태의 데이터를 만들 수 있었다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;매우 최근에 공개된 프레임워크로, 최신 아키텍쳐를 지원하는 것도 강점이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;TO DO
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;forced aligner 관련해 추가로 살펴보기&lt;/li&gt;
&lt;li&gt;강화학습 관련 논문 보기 - 데이터 스케일링의 다음 단계로 RL은 이제 그냥 필수로 붙는 것 같다. 좀 더 많이 알아야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>논문 스터디</category>
      <category>STT</category>
      <category>논문</category>
      <category>음성인식</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/507</guid>
      <comments>https://mingchin.tistory.com/507#entry507comment</comments>
      <pubDate>Wed, 11 Mar 2026 22:30:08 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 2년차를 마무리하며 - 모델러, 그리고 개발자로서의 성장</title>
      <link>https://mingchin.tistory.com/506</link>
      <description>&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;0. 들어가며&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;24년은 1년을 꽉 채워 회사를 다닌 첫 해였다. 내 밑으로도 사람이 들어오고, 위로도 한 명이 나가며 자연스럽게 역할이 커진 한 해였다. 대외적으로는 Whisper를 필두로 시장이 혼란하고 어려웠고, 그래서인지 나름 경쟁력이 있던 모델의 성능이 이슈가 되기도 했다. 사내에서 근본적으로 해결해야 하는 문제를 많이 발견하여 불만이 가득한 한 해를 보냈다고 생각했었는데, 돌아보니 늘 문제제기를 하는 데에 그치지 않고 무언가 변화와 해결을 시도했던 덕에, 꽤나 많은 것들이 달라지기 시작했다는 것이 보여 뿌듯하기도 한 1년이었다.&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;1. 올해의 keywords&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해의 키워드 3개를 꼽아보자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;모델러와 개발자로서의 성장, 선한 영향력, 지속 가능성의 향상&quot;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이라 말하고 싶다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;1-1) 모델러 &amp;amp; 개발자로서의 성장&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에는 주로 데이터를 수집/정제하여 신규로 모델을 학습하는 일에 치중하고, 개발은 이미 만들어진 무언가에 손을 보태는 정도였다면 올해는 이미 성능이 어느정도 확보된 모델을 고도화하며 보다 심도 있는 문제정의와 해결을 도모하고, 그것을 위해 필요한 개발을 주도하는 과정에서 개발 실력 자체도 많이 성장했다. 단순히 기능하는 개발이 아닌 유지보수성, 확장성, 편의성 등을 고려하는 일종의 시큐어 코딩을 하기 시작했다는 점에서 나에게 칭찬을 해주고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퇴사자가 생기며 16kHz 한국어 모델 학습을 맡게 되었고, 이미 어느정도 성능이 확보된 상황이었으나 아래의 문제가 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 추가로 append 하는 것 외에 학습 목표가 설정되지 않음&lt;/li&gt;
&lt;li&gt;일부 음절은 아예 추론이 불가능&lt;/li&gt;
&lt;li&gt;음절 인식률은 높으나, 단어 인식률이 상대적으로 떨어짐&lt;/li&gt;
&lt;li&gt;학습이 매우 불안정하여 epoch별 모델의 성능 편차가 크게 나타남, 특정 epoch에서는 비정상적으로 성능이 낮음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 영어 모델 학습 과정을 모니터링하고 분석하면서 위의 문제들에 대한 해결 방안을 어느 정도 준비해 둔 덕에, 수월하게 목표를 설정하고 성능을 향상시킬 수 있었다. 학습 목표를 인식률 향상 뿐 아니라 베이스 도메인 확장에 두고, 품질이 낮은 데이터를 걸러내는 기법을 적용해 학습 안정성 또한 확보하고자 했다. 또한 토크나이저를 개편하여 일부 꼭 필요하나 누락됐던 음절들을 포함시키고, 띄어쓰기를 기존과 다른 방식으로 접근해 단어 인식률 향상폭을 키울 수 있었다. 각 epoch별 인식 경향에 대한 분석과 평가 셋별 다르게 나타나는 양상들을 꼼꼼히 분석한 덕에 sudo labeling으로 오염된 학습 데이터를 찾아내고, 현 모델 구조의 약점을 보완할 augmentation 기법 개발의 필요성을 발견할 수 있었다. 스케줄러 변경을 통해 학습 관리를 보다 용이하게 하고, 기존 파라미터들 중 활용할 수 있는 것들을 조정해 보다 robust한 모델을 학습하는 데 성공했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌더 내 저품질 데이터 탐색이나 평균 모델 생성 등의 기능을 개발한 것 외에도 이미 타인에 의해 만들어진 프로젝트를 뜯어 고치는 과정에서 정말 많은 스트레스와 함께.. 개발 능력을 성장시킬 수 있었다. 산소는 부족할 때나 숨이 막히는 기분을 알게 되듯, 작년 VAD 개발 시 훌륭하게 구조화 및 모듈화된 프로젝트를 커스텀할 때는 몰랐던 문제들을 마주하며 다시금 개발 단계에서 중요한 것들에 대해 돌아보게 되었다. &lt;i&gt;Typehint나 docstring, dataclass 기반의 config 관리, typer 기반의 action 관리, docker를 통한 dependency 관리, 프로젝트 구조화, 기능별 함수 및 객체 분리의, logging과 예외처리 등의 중요성과 필요성, 그리고 무분별하게 복사되어 여기저기 중복돼 존재하는 코드들이 어떻게 나에게 고통을 선사할 수 있는지 &lt;/i&gt;등등.. 담담하게 나열만 했지만 정말 뼈저리고 아프게.. 중요한 것들을 체감하고 덕분에 지금까지 가졌던 좋지 않은 습관들로부터 멀어질 힘을 얻었다. 당장 기능하게 만드는 것보다 선행되어야 하는 것들이 저렇게나 많으며, 결국 &lt;b&gt;&quot;빠른&quot; 개발이 아니라 &quot;좋은&quot; 개발을 추구할 때 지수적인 효율 상승까지 따라온다&lt;/b&gt;는 것을 잊지 않아야겠다. 그렇게 경험치와 분노를 쌓은 나는 음성인식 학습기도 싹 갈아엎어 재구조화하고, 엉멍으로 개발된 코드들을 다 뜯어고치는 작업을 한 달만에 해치우며 MLOps 관련 개발을 진행할 준비를 마쳤다. 작년에는 엄두도 안났던 것이 이제는 나에게 당연한 것이 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1-2) 선한 영향력&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 파트장을 포함해 총 5명이 하나의 파트로 기능하고 있으나, 사실 4명의 파트원들은 거의 개인 프로젝트를 진행하는 것처럼 일이 분배되어 있다. 각자 무엇을 하는지 일, 주 단위로 공유하고 서로 인사이트를 주고 받기도 하고, 때로 손이 필요하면 서로 돕기도 하지만 대부분은 개인의 역량에 따라 프로젝트의 진행 속도나 완성도, 성패가 크게 좌지우지 되고 만다. 이는 규모가 크지 않은 기업에서 어느정도 필연적인 부분이지만.. 나아지는 방향으로의 변화의 바람이 나에게는 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령 git 기반 협업 및 코드 공유의 경우 작년에 도입하여 이제는 자연스러워지긴 했지만, 여전히 PR에 대한 리뷰나 이슈 등록 및 해결, github action 등을 활용한 CI/CD로의 확장 등에 대해서는 진전이 더뎠다. 모든 것이 자율에 맡겨지면 결국 &quot;하는 사람만 하는&quot; 것이 되어버리고, 좋은 문화가 자리잡기는 어렵겠다는 생각이 들었다. 그래서 간단한 작업부터 참여가 적은 파트원에게 직접 PR을 올리고 리뷰 요청을 하거나, 리뷰에 참여하여 그것이 피드백되는 과정에서 어떤 잠재적 위험이 예방될 수 있는지를 경험할 수 있도록 했다. 가장 최근에 내가 올렸던 대규모 업데이트 사항에 대한 PR에서 해당 팀원은 자발적으로 테스트에 참여해주면서 몇 가지 버그 제보와 개선사항에 대해 코멘트를 주었고, 덕분에 빠르게 반영하여 보다 나은 업데이트를 완성할 수 있었다. 구구절절 이론적 설득보다도 결국 본인이 기여하고 있다는 것과 그 기여의 효용을 직접 체감했을 때 가장 빠르게 설득이 가능하다고 믿는다.&lt;br /&gt;또한 프로젝트 진행 시 꼭 필요하지만 거의 생략되던 중요한 문서화 과정들을 팀 내 문화로 자리잡게 하기 위해 노력중이다. 너무 많은 문서는 개발의 방해 요소가 되기도 하지만, 그렇다고 아무 문서도 필요 없다는 것을 뜻하지는 않는다. 최소한의 기확안과 요구사항 정의, 기능 정의, 테스트가 제대로 이루어지고 그것이 문서로 남을 때 프로젝트가 안정적으로 진행될 수 있고, 진행된 프로젝트에 대한 복기나 피드백 역시 가능하다고 생각한다. 우리는 그 어떠한 문서도 관리자로부터 요구받지 않아, 거의 사실상 자율에 맡겨진다. 실무자 입장에서 수고를 덜 수 있다고 생각할 수 있지만, 사실 굉장히 자주 방향을 잃거나, 무언가 빠뜨리거나, 애초부터 잘못된 기획이나 설계임을 뒤늦게 깨닫거나, 문제가 발생했을 때 복기나 피드백을 요청하고 싶어도 본인 머릿 속 외에는 아무런 정보가 없으니 구두로만 소통해야 하는 등.. 훨씬 더 많은 수고가 따라오는 것이 대부분이다.&lt;br /&gt;한 개의 프로젝트에 집중할 수 있을 때는 나도 체감하지 못하던 문제들을 점점 업무가 많아지며 더 크게 느끼게 되었고, 우리 업무환경에 맞게 크게 3개 depth의 문서를 기반으로 프로젝트 진행 및 파트장과의 소통을 시도했다. 사실 이 시도는 아직까지 반쪽짜리 성공에 가깝다. 아직까지 문서 기반으로 소통하는 것의 중요성과 필요성이 채 다 어필이 되지 않은 느낌이 강하고, 팀 내에서도 다른 파트원들의 따라오려는 시도가 있었지만, 거의 나 혼자 진행하고 있다시피 한 부분이다. 그 이유는 열심히 작성해두어도 관리자 그 누구도 이에 기반에 성과를 확인해주거나 진행 상황을 이해해주지 않기 때문이 가장 크다. 그래도 해당 논의가 반복해서 이루어진 덕에, 진행 과정에서 문제가 발생했을 때 &quot;요구사항 정의가 제대로 이루어졌다면&quot;, &quot;우리가 좀 더 상세하게 정의된 기능 개발 목표에 기반해 소통할 수 있다면&quot; 과 같이 그 필요성과 효용성에 대해 더 건설적인 논의가 이루어지고 있으며, 이번에 진행되는 새 프로젝트에 한해서라도 해당 부분을 제대로 시도해보자는 요청이 받아들여질 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외에는 github action에 기반해 배포 자동화 등의 가능성을 제시하고, versioning과 모델 배포 파이프라인의 효율화의 필요성을 설득하여 이 부분에 대한 개발이 진행 중이거나 예정되어 있다. 나 혼자 하는 것이 아닌 좋은 사레나 상황을 만들어 타 팀원들이 함께할 수 있도록 영향력을 끼치는 것에서 보람을 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1-3) 지속 가능성의 향상&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2와 연결되는 이야기인데, 아직 열정이 넘치는 햇병아리로서 지속 가능성을 위한 고민을 시작했다는 점에서 만족스럽다. 여전히 성장에 목마르고, 개발이 재미있는 것은 동일하나 작년에 비해 좀 더 평균적으로 좋은 퍼포먼스를 낼 수 있도록 나를 관리하려고 노력했다. 작년에는 성과를 내야 한다는 압박과 함께, 즐거우면 끝도 없이 몰입하는 특성에 브레이크를 걸지 않아 시도때도 없이 집에 와서도 들여다보고, 휴일에도 계속 업무를 하는 일이 잦았다. 그것이 좋은 결과로 이어지는 경우도 많았지만, 때로는 지치고 스트레스가 쌓여 오히려 업무 시간에 집중력이 떨어지거나 실수가 늘어나기도 했다. 집에 와서나 휴일에 업무를 하는 것을 최대한 지양하고, 규칙적인 수면과 업무 시간을 유지하기 위해 노력했고, 그 덕에 회사에 있는 시간 동안의 평균적인 업무 효율이 좋아졌다는 느낌을 받는다. (이는 작년 말 회고에서 내가 이루고 싶었던 바와 부합하기도 한다.)&lt;br /&gt;워낙 좋아하는 것과 그렇지 않은 것에 대한 온도 차가 커서, 문서화 하는 것에 대한 거부감이 컸었는데, 동시에 진행되는 업무가 많아지며 그 필요성을 더 체감하게 되었고, 결국 장기적으로 더 좋은 개발을 하기 위해서는 귀찮더라도 문서를 남기는 것이 꼭 필요하다는 생각 하에 더 많은 시간을 투자하기 시작했다. 막상 그렇게 해보니 문서를 작성하는 시간동안 개발의 방향이나 아이디어에 대해 꼼꼼하게 정리하게 되어 하고자 하는 것에 대한 이해가 더 깊어졌다. 또 작성된 문서에 기반해 개발 중간 단계에서도 계획대로 잘 가고 있는지 추가/수정해야 할 사항은 없는지 점검하는 것이 실수를 줄이는 데에 크게 도움이 되었다. 이는 개발 일정에 대한 대략적인 책정이나 조율에도 도움이 되어, 전보다 더 내가 특정 업무를 얼마의 시간동안 할 수 있는지에 대해 세분화하여 그려볼 수 있게 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 전에는 나의 업무가 아니더라도 파트 내에 벌어지는 모든 일들에 대해 관심가지며, 문제가 발생하지 않도록 하는 데에 많은 노력을 기울였다. 타 파트원이 진행하는 학습을 계속 모니터링 한다든가, 보고 자료에 기반해 조언을 한다든가. 의도 자체는 좋으나 타 팀원의 성장할 수 있는 기회를 제한하는 느낌도 있고, 나의 한정적인 에너지가 분산되어 같은 일도 더 힘들게 진행하게 되는 부작용이 있었다. 좀 더 나의 업무와 파트 내 중요한 일들에 집중하려고 노력하면서, 보다 긴 호흡에서 지속 가능한 형태로 나의 업무 습관과 환경을 만들어나가고 있다고 생각한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;2. 3년차의 나에게 필요한 것&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2-1)&amp;nbsp;더 작은 단위의 개발 진행 및 PR 요청하기&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최근 한 구글 시니어 개발자의 영상을 인상 깊게 봤다. &quot;업무를 얼마나 작은 단위로 잘게 쪼갤 수 있느냐&quot;가 중요한 능력 중에 하나라는.. 올해 짧은 기간에 많은 개발을 진행해야 한다는 압박과 욕심 때문에, 대규모 업데이트를 너무 짧은 시간에 하나의 PR에서 다루면서 크고 작은 실수가 많이 발생하고, 아쉬움이 컸다. 이는 나 개인의 잘못보다도 올바른 과정에 대한 호소가 가 닿지 않는 사내 문화와 &quot;새로운 프로덕트&quot;만을 원하는 관리자 등 여러 환경적 요인에 기인하는 것이긴 하지만, 그러니 더더욱 나 자신이 좋은 개발자로 성장하는 데 있어 좋지 못한 습관이 몸에 자리잡지 않도록 노력하고 싶다.&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2-2) 새로운 기술 스택의 장착&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모델 배포 자동화나 엔진 개발 등 마침 올해 좋은 기회들이 있다. 사내에서 새 도전이 꺼려지는 이유 중에 하나는 과정으로의 실패나 좌절에 대한 여유가 주어지지 않는다는 점이다. 모델 배포 자동화의 경우에도 정말 중요한 아젠다임에도 고작 3주의 시간이 주어졌고.. 화자 분리 엔진 개발의 경우에도 새로운 언어에 적응하고 해보지 않은 엔진에 대한 도전임에도 정말 짧은 시간이 주어졌다. 기간 내에 그럴듯한 결과물을 내기 위해서는 하드코딩 위주의 수준 낮은 개발이 이루어지기 마련인데, 이번에는 VAD 개발 때의 실수를 반복하고 싶지 않다. (빌더를 &quot;동작만 할 수 있게&quot; 남겨둔 덕에 하자가 많다.) &quot;해봤다&quot;가 아닌 나의 새로운 기술 스택이 될 수 있도록, 올바른 과정으로 소화하고 경험하여 내 것으로 만들 수 있도록 노력하고 싶다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2-3) 글로 남기기&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;항상 완성도가 떨어진다고 느껴 글쓰기가 꺼려지는데, 보다 많은 글을 남겨야 한다. 결국 텍스트화 하는 과정에서 한 번더 머릿속에서 정리되기도 하고, 나중에 다시 쉽게 떠올릴 수 있는 기반이 된다. 포트폴리오를 업데이트 하는 것을 포함해, 보다 많은 것들을 글로 남겨 내 자산으로 만드는 한해가 되기를!&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고, 기록/업무</category>
      <category>2년차</category>
      <category>개발자회고</category>
      <category>회고</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/506</guid>
      <comments>https://mingchin.tistory.com/506#entry506comment</comments>
      <pubDate>Sun, 9 Feb 2025 21:42:23 +0900</pubDate>
    </item>
    <item>
      <title>[Rust] usize, 익명 함수 클로저와 기본 메서드</title>
      <link>https://mingchin.tistory.com/505</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mingchin.tistory.com/504&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서 구현했던 함수를 뜯어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;usize&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust는 함수의 output type을 꼭 지정해주어야 한다. &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;&quot;unsigned size&quot;의 준말로, 시스템 포인터 크기와 동일한 크기를 가지는 unsigned integer다. 요즘엔 거의 다 64bit 시스템을 사용하니 8바이트라 보면 되겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #242424; text-align: left;&quot;&gt; &lt;span style=&quot;color: #383a42; text-align: start;&quot;&gt;hyp.&lt;/span&gt;&lt;span style=&quot;color: #c18401; text-align: start;&quot;&gt;chars&lt;/span&gt;&lt;span style=&quot;color: #383a42; text-align: start;&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color: #c18401; text-align: start;&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color: #383a42; text-align: start;&quot;&gt;(|c| !c.&lt;/span&gt;&lt;span style=&quot;color: #c18401; text-align: start;&quot;&gt;is_whitespace&lt;/span&gt;&lt;span style=&quot;color: #383a42; text-align: start;&quot;&gt;()).&lt;/span&gt;&lt;span style=&quot;color: #c18401; text-align: start;&quot;&gt;collect&lt;/span&gt;&lt;span style=&quot;color: #383a42; text-align: start;&quot;&gt;();&lt;/span&gt; &lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 파이써닉한.. 표현이 놀랍다. 하나씩 뜯어보면 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;hyp.chars()&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;hyp&lt;span&gt;&amp;nbsp;&lt;/span&gt;문자열을 문자(char) 단위로 분리하여 이터레이터를 생성합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;예:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;hello world&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;.filter(|c| !c.is_whitespace())&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;이터레이터에서 각 문자를 검사하여 공백 문자가 아닌 문자만 남깁니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;|c|는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;클로저(익명 함수)로&lt;/b&gt;&lt;/span&gt;, 각 문자&lt;span&gt;&amp;nbsp;&lt;/span&gt;c를 받아서&lt;span&gt;&amp;nbsp;&lt;/span&gt;!c.is_whitespace()&lt;span&gt;&amp;nbsp;&lt;/span&gt;조건을 검사합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;is_whitespace()는 문자가 공백인지 확인하는 메서드입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;예:&lt;span&gt;&amp;nbsp;&lt;/span&gt;['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;.collect()&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;필터링된 문자들을 모아서 새로운 컬렉션(예: 문자열)으로 만듭니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;기본적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;collect()는 이터레이터의 요소들을 모아 &lt;b&gt;벡터(Vec)로 반환하지만, 타입 힌트를 주면 문자열로도 변환&lt;/b&gt;할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1733233556639&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let hyp = &quot;hello world&quot;;
let result: String = hyp.chars().filter(|c| !c.is_whitespace()).collect();
println!(&quot;{}&quot;, result); // 출력: &quot;helloworld&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드상에서, result 라는 변수에도 형을 지정하고 값을 할당하는 표현이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;익명 함수, 클로저&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 lambda와 유사한 역할을 하는 것으로 보인다. &quot; &lt;b&gt;|c| = lambda c:&quot;&lt;/b&gt; 와 동일한 표현이며, 변수는 아래 3가지 방법으로 할당(캡처)할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;참조로 캡처&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;amp;T&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 클로저가 변수를 읽기 전용으로 캡처합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;가변 참조로 캡처&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;amp;mut T&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 클로저가 변수를 수정할 수 있도록 캡처합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;값으로 캡처&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;T&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 클로저가 변수를 소유하도록 캡처합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로는 1의 참조로 캡처를 시도하며, 값으로 캡처를 하고 싶은 경우에는 &quot;move&quot;라는 별도 메서드를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1733235059124&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let haystack = vec![1, 2, 3];
let contains = move |needle| haystack.contains(needle);

println!(&quot;{}&quot;, contains(&amp;amp;1)); // true
println!(&quot;{}&quot;, contains(&amp;amp;4)); // false

// haystack을 다시 사용하려고 하면 컴파일 오류가 발생합니다.
// println!(&quot;There are {} elements in vec&quot;, haystack.len()); // 오류 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저가 변수의 소유권을 가져간다는 사실이 흥미롭다. 아마 사용과 동시에 변수를 해제하는? 느낌인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타입 캐스팅 as&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #242424; text-align: left;&quot;&gt;&quot;cer = (substitution_count + insertion_count + deletion_count) as f64&quot; 와 같이 integer의 합산으로 계산한 결과를 f64 형으로 변환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #242424; text-align: left;&quot;&gt;문자열의 할당 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;String::new()&lt;/span&gt; &lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #242424; text-align: left;&quot;&gt;빈 문자열을 할당하는 방법은 여러 가지가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733236331707&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1
let s = String::from(&quot;&quot;);

# 2
let s = &quot;&quot;.to_string();

# 3
let s = String::with_capacity(0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String::new()&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;는 초기 버퍼를 할당하지 않는 mutable 객체로, 할당 시 메모리 비용이 저렴하다. 3의 방법과 동일하며, 0 대신 일정 바이트를 할당하는 경우 처음부터 그만큼의 메모리를 사용하게 되는데, 이는 추후 문자열을 추가할 때 재할당을 하지 않는 다는 점에서 차이가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt; &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;io::stdout().flush().unwrap()&lt;/span&gt; &lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;color: #242424;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;flush()는 버퍼에 쌓인 데이터를 즉시 출력하거나 전송한다고 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733236791116&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use std::io::{self, Write};

fn main() {
    print!(&quot;Enter your name: &quot;);
    // io::stdout().flush().unwrap(); // 이 줄을 주석 처리하면 메시지가 즉시 출력되지 않을 수 있습니다.
    let mut name = String::new();
    io::stdin().read_line(&amp;amp;mut name).unwrap();
    println!(&quot;Hello, {}!&quot;, name.trim());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 이렇게 실행해보니, 사용자 입력을 기다리는 동안 &quot;Enter your name: &quot;의 메시지가 출력되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;unwrap()의 경우 해당 앞의 메서드가 정상적으로 실행되었는지 체크하는 예외처리로, 에러가 발생할 경우 프로그램을 패닉 상태로 만든다. (sys.exit()과 유사한 것 같다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733236908860&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;io::stdout().flush().expect(&quot;Failed to flush stdout&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;unwrap()이 아니라 expect()로 실행할 경우, 예외 발생에 대한 로깅을 할 수 있다.&lt;/p&gt;</description>
      <category>AI/Rust</category>
      <category>Rust</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/505</guid>
      <comments>https://mingchin.tistory.com/505#entry505comment</comments>
      <pubDate>Tue, 3 Dec 2024 23:43:04 +0900</pubDate>
    </item>
    <item>
      <title>[Rust] CER과 WER 계산하기</title>
      <link>https://mingchin.tistory.com/504</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;세부 목표 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust 언어를 보다 재밌고 효율적으로 공부하기 위해 아래의 기능을 가진 안드로이드 앱을 만들어 볼 예정이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 개의 빈 텍스트를 입력할 수 있는 공간을 가짐&lt;/li&gt;
&lt;li&gt;셋 중 하나의 eval metric을 설정 가능(CER / WER / ALL)&lt;/li&gt;
&lt;li&gt;두 개의 문장을 입력 후, &quot;calculate&quot;을 누르면 두 문장의 metric을 연산 후 TMSID를 포함해 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 기능이지만 난 안드로이드의 a도 모르기 때문에.. 험난한 여정이 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;CER / WER 연산하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Copilot에게 CER에 대해서 설명하고, 띄어쓰기의 경우 음절에서 배제해야 한다는 사실을 말해주었더니 그럭저럭 기능하는 코드를 주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1732282128954&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use std::io::{self, Write};

struct CerResult {
    total: usize,
    match_count: usize,
    substitution_count: usize,
    insertion_count: usize,
    deletion_count: usize,
    cer: f64,
}

fn calculate_errors(hyp: &amp;amp;str, ref_: &amp;amp;str) -&amp;gt; (usize, usize, usize, usize) {
    let hyp_chars: Vec&amp;lt;char&amp;gt; = hyp.chars().filter(|c| !c.is_whitespace()).collect();
    let ref_chars: Vec&amp;lt;char&amp;gt; = ref_.chars().filter(|c| !c.is_whitespace()).collect();
    let mut match_count = 0;
    let mut substitution_count = 0;
    let mut insertion_count = 0;
    let mut deletion_count = 0;
    
    let mut i = 0;
    let mut j = 0;
    
    while i &amp;lt; hyp_chars.len() &amp;amp;&amp;amp; j &amp;lt; ref_chars.len() {
        if hyp_chars[i] == ref_chars[j] {
            match_count += 1;
            i += 1;
            j += 1;
        } else {
            substitution_count += 1;
            i += 1;
            j += 1;
        }
    }
    
    if i &amp;lt; hyp_chars.len() {
        insertion_count = hyp_chars.len() - i;
    }
    
    if j &amp;lt; ref_chars.len() {
        deletion_count = ref_chars.len() - j;
    }
    
    (match_count, substitution_count, insertion_count, deletion_count)
}

fn calculate_cer(hyp: &amp;amp;str, ref_: &amp;amp;str) -&amp;gt; CerResult {
    let (match_count, substitution_count, insertion_count, deletion_count) = calculate_errors(hyp, ref_);
    let total_chars = ref_.chars().filter(|c| !c.is_whitespace()).count();
    let cer = (substitution_count + insertion_count + deletion_count) as f64 / total_chars as f64;
    
    CerResult {
        total: total_chars,
        match_count,
        substitution_count,
        insertion_count,
        deletion_count,
        cer,
    }
}

fn main() {
    let mut ref_input = String::new();
    let mut hyp_input = String::new();
    
    print!(&quot;Enter the reference sentence: &quot;);
    io::stdout().flush().unwrap();
    io::stdin().read_line(&amp;amp;mut ref_input).unwrap();
    let ref_input = ref_input.trim();
    
    print!(&quot;Enter the hypothesis sentence: &quot;);
    io::stdout().flush().unwrap();
    io::stdin().read_line(&amp;amp;mut hyp_input).unwrap();
    let hyp_input = hyp_input.trim();
    
    let result = calculate_cer(hyp_input, ref_input);
    
    println!(&quot;Total: {}&quot;, result.total);
    println!(&quot;Match: {}&quot;, result.match_count);
    println!(&quot;Substitution: {}&quot;, result.substitution_count);
    println!(&quot;Insertion: {}&quot;, result.insertion_count);
    println!(&quot;Deletion: {}&quot;, result.deletion_count);
    println!(&quot;CER: {:.4}&quot;, result.cer);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;ref&quot;의 경우 예약어로 사용할 수 없어 ref_로 대체를 요청했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 살펴보니.. 앞 음절부터 순차적으로 탐색하기 때문에, 정확한 편집거리를 계산해 줄 수 없는 상태였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OsjZp/btsKTohKou3/Gm7OabpAtM6O1P4vSyBxuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OsjZp/btsKTohKou3/Gm7OabpAtM6O1P4vSyBxuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OsjZp/btsKTohKou3/Gm7OabpAtM6O1P4vSyBxuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOsjZp%2FbtsKTohKou3%2FGm7OabpAtM6O1P4vSyBxuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;491&quot; height=&quot;185&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 위와 같은 오류를 범하게 된다. 그래서 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;Levenshtein&lt;span&gt; edit distance의 개념을 사용해 음절을 연산해달라고 요청했다. 이를 위해서는 외부 패키지인 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;edit_distance하고, 그래서 이를 Catgo.toml 내 dependency에 아래와 같이 추가해준다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732283040275&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[dependencies]
edit-distance = &quot;2.0&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q6s2O/btsKVbIcdd2/JAozbTtFGi0b0dV4MmPXp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q6s2O/btsKVbIcdd2/JAozbTtFGi0b0dV4MmPXp0/img.png&quot; data-alt=&quot;????&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q6s2O/btsKVbIcdd2/JAozbTtFGi0b0dV4MmPXp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq6s2O%2FbtsKVbIcdd2%2FJAozbTtFGi0b0dV4MmPXp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;116&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;????&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;br /&gt;이 문제는 chars() 메서드가 UTF-8 문자로 문자열을 처리하기 때문에 발생할 수 있습니다. chars() 메서드는 각 문자를 UTF-8 코드 포인트로 처리하므로, 한글 문자 하나가 여러 개의 코드 포인트로 인식될 수 있습니다. 이를 해결하기 위해&amp;nbsp;&lt;br /&gt;graphemes 메서드를 사용하여 유니코드 그래프 클러스터를 처리할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오.. UTF8에서 한글이 3음절로 다루어지기 때문에 Total이 3배가 돼서 나온 것 같다. 그래서 이를 해결한 최종 코드는&lt;/p&gt;
&lt;pre id=&quot;code_1732283203778&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use std::io::{self, Write};
use edit_distance::edit_distance;
use unicode_segmentation::UnicodeSegmentation;

struct CerResult {
    total: usize,
    match_count: usize,
    substitution_count: usize,
    insertion_count: usize,
    deletion_count: usize,
    cer: f64,
}

fn calculate_cer(hyp: &amp;amp;str, ref_: &amp;amp;str) -&amp;gt; CerResult {
    let hyp_chars: Vec&amp;lt;&amp;amp;str&amp;gt; = hyp.graphemes(true).filter(|c| !c.chars().all(char::is_whitespace)).collect();
    let ref_chars: Vec&amp;lt;&amp;amp;str&amp;gt; = ref_.graphemes(true).filter(|c| !c.chars().all(char::is_whitespace)).collect();
    
    let hyp_str: String = hyp_chars.concat();
    let ref_str: String = ref_chars.concat();
    
    let distance = edit_distance(&amp;amp;hyp_str, &amp;amp;ref_str);
    let total_chars = ref_chars.len();
    let cer = distance as f64 / total_chars as f64;
    
    let mut match_count = 0;
    let mut substitution_count = 0;
    let mut insertion_count = 0;
    let mut deletion_count = 0;
    
    let mut i = 0;
    let mut j = 0;
    
    while i &amp;lt; hyp_chars.len() &amp;amp;&amp;amp; j &amp;lt; ref_chars.len() {
        if hyp_chars[i] == ref_chars[j] {
            match_count += 1;
            i += 1;
            j += 1;
        } else {
            substitution_count += 1;
            i += 1;
            j += 1;
        }
    }
    
    if i &amp;lt; hyp_chars.len() {
        insertion_count = hyp_chars.len() - i;
    }
    
    if j &amp;lt; ref_chars.len() {
        deletion_count = ref_chars.len() - j;
    }
    
    CerResult {
        total: total_chars,
        match_count,
        substitution_count,
        insertion_count,
        deletion_count,
        cer,
    }
}

fn main() {
    let mut ref_input = String::new();
    let mut hyp_input = String::new();
    
    print!(&quot;Enter the reference sentence: &quot;);
    io::stdout().flush().unwrap();
    io::stdin().read_line(&amp;amp;mut ref_input).unwrap();
    let ref_input = ref_input.trim();
    
    print!(&quot;Enter the hypothesis sentence: &quot;);
    io::stdout().flush().unwrap();
    io::stdin().read_line(&amp;amp;mut hyp_input).unwrap();
    let hyp_input = hyp_input.trim();
    
    let result = calculate_cer(hyp_input, ref_input);
    
    println!(&quot;Total: {}&quot;, result.total);
    println!(&quot;Match: {}&quot;, result.match_count);
    println!(&quot;Substitution: {}&quot;, result.substitution_count);
    println!(&quot;Insertion: {}&quot;, result.insertion_count);
    println!(&quot;Deletion: {}&quot;, result.deletion_count);
    println!(&quot;Total Errors: {}&quot;, result.substitution_count + result.insertion_count + result.deletion_count);
    println!(&quot;CER: {:.4}&quot;, result.cer);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 자세히 보니.. 다시 앞에서부터 순차 계산을 하는 코드를 뱉어내며 레퍼런스를 던져주고 뭔 짓을 해봐도 같은 코드를 뱉어내기 시작했다. 그래서 클로드를 써보기로..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 첫 구현체부터 훨씬 나은 모습을 보여줬다. 직접 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;Levenshtein&lt;/span&gt;&lt;span style=&quot;color: #242424; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;edit distance 계산을 구현하고, 그 결과를 출력하고자 했다. 하지만 유사하게 encoding 관련 오차가 있었고, 이를 지적해도 제대로 해결되지 않았다. 그레서 아래의 &lt;a href=&quot;https://github.com/NVIDIA/NeMo/blob/main/nemo/collections/asr/metrics/wer.py&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;파이썬 레퍼런스&lt;/a&gt;를 던져주고, 이걸 참고해서 수정해보라고 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732284698544&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def word_error_rate_detail(
    hypotheses: List[str], references: List[str], use_cer=False
) -&amp;gt; Tuple[float, int, float, float, float]:
    &quot;&quot;&quot;
    Computes Average Word Error Rate with details (insertion rate, deletion rate, substitution rate)
    between two texts represented as corresponding lists of string.

    Hypotheses and references must have same length.

    Args:
        hypotheses (list): list of hypotheses
        references(list) : list of references
        use_cer (bool): set True to enable cer

    Returns:
        wer (float): average word error rate
        words (int):  Total number of words/charactors of given reference texts
        ins_rate (float): average insertion error rate
        del_rate (float): average deletion error rate
        sub_rate (float): average substitution error rate
    &quot;&quot;&quot;
    scores = 0
    words = 0
    ops_count = {'substitutions': 0, 'insertions': 0, 'deletions': 0}

    if len(hypotheses) != len(references):
        raise ValueError(
            &quot;In word error rate calculation, hypotheses and reference&quot;
            &quot; lists must have the same number of elements. But I got:&quot;
            &quot;{0} and {1} correspondingly&quot;.format(len(hypotheses), len(references))
        )

    for h, r in zip(hypotheses, references):
        if use_cer:
            h_list = list(h)
            r_list = list(r)
        else:
            h_list = h.split()
            r_list = r.split()

        # To get rid of the issue that jiwer does not allow empty string
        if len(r_list) == 0:
            if len(h_list) != 0:
                errors = len(h_list)
                ops_count['insertions'] += errors
            else:
                errors = 0
        else:
            if use_cer:
                measures = jiwer.cer(r, h, return_dict=True)
            else:
                measures = jiwer.compute_measures(r, h)

            errors = measures['insertions'] + measures['deletions'] + measures['substitutions']
            ops_count['insertions'] += measures['insertions']
            ops_count['deletions'] += measures['deletions']
            ops_count['substitutions'] += measures['substitutions']

        scores += errors
        words += len(r_list)

    if words != 0:
        wer = 1.0 * scores / words
        ins_rate = 1.0 * ops_count['insertions'] / words
        del_rate = 1.0 * ops_count['deletions'] / words
        sub_rate = 1.0 * ops_count['substitutions'] / words
    else:
        wer, ins_rate, del_rate, sub_rate = float('inf'), float('inf'), float('inf'), float('inf')

    return wer, words, ins_rate, del_rate, sub_rate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그랬더니, 마지막 출력부 정도만 원하는 대로 조금 손보면 되는 코드를 잘 던져주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1732284757713&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use std::io;
use unicode_segmentation::UnicodeSegmentation;

fn compute_error_rate(hyp: &amp;amp;str, ref_str: &amp;amp;str, use_cer: bool) -&amp;gt; (usize, usize, usize, usize) {
    let (hyp_list, ref_list) = if use_cer {
        (
            hyp.graphemes(true).collect::&amp;lt;Vec&amp;lt;&amp;amp;str&amp;gt;&amp;gt;(),
            ref_str.graphemes(true).collect::&amp;lt;Vec&amp;lt;&amp;amp;str&amp;gt;&amp;gt;()
        )
    } else {
        (
            hyp.split_whitespace().collect::&amp;lt;Vec&amp;lt;&amp;amp;str&amp;gt;&amp;gt;(),
            ref_str.split_whitespace().collect::&amp;lt;Vec&amp;lt;&amp;amp;str&amp;gt;&amp;gt;()
        )
    };

    if ref_list.is_empty() {
        return if hyp_list.is_empty() {
            (0, 0, 0, 0)  // No errors
        } else {
            (hyp_list.len(), hyp_list.len(), 0, 0)  // All insertions
        };
    }

    let m = hyp_list.len();
    let n = ref_list.len();
    
    // 레벤슈타인 거리 행렬
    let mut dp = vec![vec![0; n + 1]; m + 1];
    for i in 0..=m { dp[i][0] = i; }
    for j in 0..=n { dp[0][j] = j; }

    // 작업 추적 행렬
    let mut ops = vec![vec![0; n + 1]; m + 1];

    for i in 1..=m {
        for j in 1..=n {
            if hyp_list[i-1] == ref_list[j-1] {
                dp[i][j] = dp[i-1][j-1];
                ops[i][j] = 0;  // 매치
            } else {
                let sub = dp[i-1][j-1] + 1;
                let del = dp[i-1][j] + 1;
                let ins = dp[i][j-1] + 1;

                dp[i][j] = sub.min(del).min(ins);
                ops[i][j] = if dp[i][j] == sub { 1 }  // 대체
                else if dp[i][j] == del { 2 }  // 삭제
                else { 3 };  // 삽입
            }
        }
    }

    // 오류 계산
    let mut substitutions = 0;
    let mut deletions = 0;
    let mut insertions = 0;
    let errors = dp[m][n];

    // 역추적으로 오류 타입 분류
    let mut i = m;
    let mut j = n;
    while i &amp;gt; 0 &amp;amp;&amp;amp; j &amp;gt; 0 {
        match ops[i][j] {
            1 =&amp;gt; { substitutions += 1; i -= 1; j -= 1; }
            2 =&amp;gt; { deletions += 1; i -= 1; }
            3 =&amp;gt; { insertions += 1; j -= 1; }
            _ =&amp;gt; { i -= 1; j -= 1; }
        }
    }

    // 남은 삽입/삭제 처리
    insertions += j;
    deletions += i;

    (errors, insertions, deletions, substitutions)
}

fn calculate_error_rate(hypotheses: &amp;amp;[String], references: &amp;amp;[String], use_cer: bool) -&amp;gt; (f64, usize, usize, usize, usize) {
    let mut total_errors = 0;
    let mut total_words = 0;
    let mut total_insertions = 0;
    let mut total_deletions = 0;
    let mut total_substitutions = 0;

    for (h, r) in hypotheses.iter().zip(references.iter()) {
        let (errors, insertions, deletions, substitutions) = compute_error_rate(h, r, use_cer);
        
        total_errors += errors;
        total_words += if use_cer { 
            r.graphemes(true).count() 
        } else { 
            r.split_whitespace().count() 
        };
        total_insertions += insertions;
        total_deletions += deletions;
        total_substitutions += substitutions;
    }

    let error_rate = if total_words &amp;gt; 0 {
        total_errors as f64 / total_words as f64
    } else {
        f64::INFINITY
    };

    (error_rate, total_words, total_insertions, total_deletions, total_substitutions)
}

fn main() {
    println!(&quot;Enter the hypothesis sentence:&quot;);
    let mut hyp = String::new();
    io::stdin().read_line(&amp;amp;mut hyp).expect(&quot;Failed to read line&quot;);
    let hyp = hyp.trim().to_string();
    
    println!(&quot;Enter the reference sentence:&quot;);
    let mut ref_str = String::new();
    io::stdin().read_line(&amp;amp;mut ref_str).expect(&quot;Failed to read line&quot;);
    let ref_str = ref_str.trim().to_string();
    
    let (error_rate, total_words, total_insertions, total_deletions, total_substitutions) = 
        calculate_error_rate(&amp;amp;[hyp], &amp;amp;[ref_str], true);
    
    println!(&quot;\nCER Calculation Results:&quot;);
    println!(&quot;CER: {:.2}%&quot;, error_rate * 100.0);
    println!(&quot;Total Chars: {}&quot;, total_words);
    println!(&quot;Total Ins: {}&quot;, total_insertions);
    println!(&quot;Total Del: {}&quot;, total_deletions);
    println!(&quot;Total Sub: {}&quot;, total_substitutions);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀라운 것은 &quot; jiwer 라이브러리의 기능을 직접 구현해야 할 것 같네요. &quot; 라고 말한 뒤 세부 error 계산을 위한 로직을 알아서 구현했다는 점.. 하지만 그럼에도 정확한 구현은 아니었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGhD5f/btsKTCNAGOB/dD9kud0zCMdjNZaa8reKhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGhD5f/btsKTCNAGOB/dD9kud0zCMdjNZaa8reKhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGhD5f/btsKTCNAGOB/dD9kud0zCMdjNZaa8reKhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGhD5f%2FbtsKTCNAGOB%2FdD9kud0zCMdjNZaa8reKhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;382&quot; height=&quot;272&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 세부 연산까지 포함한 레퍼런스까지 던져준 후에야 정확한 코드를 받을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732286612647&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use std::collections::HashMap;
use unicode_segmentation::UnicodeSegmentation;
use std::io::{self};

#[derive(Debug)]
struct EditResult {
    distance: Vec&amp;lt;Vec&amp;lt;usize&amp;gt;&amp;gt;,
    steps: Vec&amp;lt;char&amp;gt;,
}

fn edit_distance(reference: &amp;amp;[&amp;amp;str], hypothesis: &amp;amp;[&amp;amp;str]) -&amp;gt; EditResult {
    let m = reference.len();
    let n = hypothesis.len();
    let mut d = vec![vec![0; n + 1]; m + 1];

    // Initialize first row and column
    for i in 0..=m {
        d[i][0] = i;
    }
    for j in 0..=n {
        d[0][j] = j;
    }

    // Compute edit distance matrix
    for i in 1..=m {
        for j in 1..=n {
            if reference[i - 1] == hypothesis[j - 1] {
                d[i][j] = d[i - 1][j - 1];
            } else {
                let substitute = d[i - 1][j - 1] + 1;
                let insert = d[i][j - 1] + 1;
                let delete = d[i - 1][j] + 1;
                d[i][j] = substitute.min(insert).min(delete);
            }
        }
    }

    // Backtrack to get steps
    let mut steps = Vec::new();
    let mut x = m;
    let mut y = n;
    while x &amp;gt; 0 || y &amp;gt; 0 {
        if x &amp;gt; 0 &amp;amp;&amp;amp; y &amp;gt; 0 &amp;amp;&amp;amp; reference[x - 1] == hypothesis[y - 1] {
            steps.push('e');
            x -= 1;
            y -= 1;
        } else if y &amp;gt; 0 &amp;amp;&amp;amp; d[x][y] == d[x][y - 1] + 1 {
            steps.push('i');
            y -= 1;
        } else if x &amp;gt; 0 &amp;amp;&amp;amp; y &amp;gt; 0 &amp;amp;&amp;amp; d[x][y] == d[x - 1][y - 1] + 1 {
            steps.push('s');
            x -= 1;
            y -= 1;
        } else if x &amp;gt; 0 {
            steps.push('d');
            x -= 1;
        }
    }
    steps.reverse();

    EditResult {
        distance: d,
        steps,
    }
}

fn calculate_cer(reference: &amp;amp;str, hypothesis: &amp;amp;str) -&amp;gt; HashMap&amp;lt;String, f64&amp;gt; {
    let ref_chars: Vec&amp;lt;&amp;amp;str&amp;gt; = reference.graphemes(true).collect();
    let hyp_chars: Vec&amp;lt;&amp;amp;str&amp;gt; = hypothesis.graphemes(true).collect();

    let result = edit_distance(&amp;amp;ref_chars, &amp;amp;hyp_chars);
    let steps = result.steps;
    let distance = result.distance[ref_chars.len()][hyp_chars.len()];

    let total = ref_chars.len();
    let counter: HashMap&amp;lt;char, usize&amp;gt; = steps.iter()
        .fold(HashMap::new(), |mut acc, &amp;amp;step| {
            *acc.entry(step).or_insert(0) += 1;
            acc
        });

    let error_rate = if total &amp;gt; 0 {
        (distance as f64 / total as f64) * 100.0
    } else {
        0.0
    };

    HashMap::from([
        (&quot;cer&quot;.to_string(), error_rate),
        (&quot;tot&quot;.to_string(), total as f64),
        (&quot;mat&quot;.to_string(), *counter.get(&amp;amp;'e').unwrap_or(&amp;amp;0) as f64),
        (&quot;sub&quot;.to_string(), *counter.get(&amp;amp;'s').unwrap_or(&amp;amp;0) as f64),
        (&quot;del&quot;.to_string(), *counter.get(&amp;amp;'d').unwrap_or(&amp;amp;0) as f64),
        (&quot;ins&quot;.to_string(), *counter.get(&amp;amp;'i').unwrap_or(&amp;amp;0) as f64),
    ])
}

fn calculate_wer(reference: &amp;amp;str, hypothesis: &amp;amp;str) -&amp;gt; HashMap&amp;lt;String, f64&amp;gt; {
    let ref_words: Vec&amp;lt;&amp;amp;str&amp;gt; = reference.split_whitespace().collect();
    let hyp_words: Vec&amp;lt;&amp;amp;str&amp;gt; = hypothesis.split_whitespace().collect();

    let result = edit_distance(&amp;amp;ref_words, &amp;amp;hyp_words);
    let steps = result.steps;
    let distance = result.distance[ref_words.len()][hyp_words.len()];

    let total = ref_words.len();
    let counter: HashMap&amp;lt;char, usize&amp;gt; = steps.iter()
        .fold(HashMap::new(), |mut acc, &amp;amp;step| {
            *acc.entry(step).or_insert(0) += 1;
            acc
        });

    let error_rate = if total &amp;gt; 0 {
        (distance as f64 / total as f64) * 100.0
    } else {
        0.0
    };

    HashMap::from([
        (&quot;wer&quot;.to_string(), error_rate),
        (&quot;tot&quot;.to_string(), total as f64),
        (&quot;mat&quot;.to_string(), *counter.get(&amp;amp;'e').unwrap_or(&amp;amp;0) as f64),
        (&quot;sub&quot;.to_string(), *counter.get(&amp;amp;'s').unwrap_or(&amp;amp;0) as f64),
        (&quot;del&quot;.to_string(), *counter.get(&amp;amp;'d').unwrap_or(&amp;amp;0) as f64),
        (&quot;ins&quot;.to_string(), *counter.get(&amp;amp;'i').unwrap_or(&amp;amp;0) as f64),
    ])
}

fn main() {
    println!(&quot;Enter the hypothesis sentence:&quot;);
    let mut hyp = String::new();
    io::stdin().read_line(&amp;amp;mut hyp).expect(&quot;Failed to read line&quot;);
    let hyp = hyp.trim();
    
    println!(&quot;Enter the reference sentence:&quot;);
    let mut ref_str = String::new();
    io::stdin().read_line(&amp;amp;mut ref_str).expect(&quot;Failed to read line&quot;);
    let ref_str = ref_str.trim();

    let cer_result = calculate_cer(ref_str, hyp);
    let wer_result = calculate_wer(ref_str, hyp);

    println!(&quot;CER Results: {:?}&quot;, cer_result);
    println!(&quot;WER Results: {:?}&quot;, wer_result);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs5nPp/btsKT01IxG7/JefUWVrLDCj9HiyVscN6ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs5nPp/btsKT01IxG7/JefUWVrLDCj9HiyVscN6ck/img.png&quot; data-alt=&quot;드디어 정확한 결과가 나왔다ㅠㅠ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs5nPp/btsKT01IxG7/JefUWVrLDCj9HiyVscN6ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs5nPp%2FbtsKT01IxG7%2FJefUWVrLDCj9HiyVscN6ck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1058&quot; height=&quot;142&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;드디어 정확한 결과가 나왔다ㅠㅠ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 오늘 해보면서 느낀점은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Copilot 보단 클로드가 코딩을 훨씬 잘한다. 그래도 결국 자꾸 틀린 부분을 찾아내고, 보완해주어야 하는 점을 구체적으로 지적하고 레퍼런스를 제공해야만 원하는 지점에 도달할 수 있다.&lt;/li&gt;
&lt;li&gt;생각했던 것보다 Rust 구현체가 간결하고 직관적인게 python과 겹쳐보이는 구석이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 생각보다 너무 길어져서.. 다음에는 이 완성된 코드를 뜯어보며 변수의 형 정의나 특정 메서드들에 대해 공부해봐야겠다.&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/Rust</category>
      <category>CER</category>
      <category>Rust</category>
      <category>WER</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/504</guid>
      <comments>https://mingchin.tistory.com/504#entry504comment</comments>
      <pubDate>Fri, 22 Nov 2024 23:48:15 +0900</pubDate>
    </item>
    <item>
      <title>[Rust] 조건문과 반복문 - let if, loop, match</title>
      <link>https://mingchin.tistory.com/503</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Rust의 기본적인 조건문과 반복문은 C 계열 언어와 거의 동일하다. 일부 C와 python이 조합된 것 같은.. Rust 만의 특성을 가지는 몇 가지를 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;let if&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 지정하는 let과 조건 분기를 조합해서 아래와 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1732018491355&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use std::io;

fn main() {
    println!(&quot;Please enter the temperature:&quot;);

    let mut input = String::new();
    io::stdin().read_line(&amp;amp;mut input)
        .expect(&quot;Failed to read line&quot;);

    let temperature: i32 = input.trim().parse()
        .expect(&quot;Please type a number!&quot;);

    let weather = if temperature &amp;gt; 25 {
        &quot;hot&quot;
    } else if temperature &amp;gt; 15 {
        &quot;warm&quot;
    } else {
        &quot;cold&quot;
    };

    println!(&quot;The weather is: {}&quot;, weather);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 작성하고 실행해보면, 동적으로 입력된 stdin에 따라 &quot;weather&quot; 변수가 셋 중 하나로 할당된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfNMmJ/btsKQcfTHMa/WDNXtSLB4UgW6N2KoNwgBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfNMmJ/btsKQcfTHMa/WDNXtSLB4UgW6N2KoNwgBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfNMmJ/btsKQcfTHMa/WDNXtSLB4UgW6N2KoNwgBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfNMmJ%2FbtsKQcfTHMa%2FWDNXtSLB4UgW6N2KoNwgBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;67&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rb9ou/btsKOzp7wdX/DjBVJ44VTUpjTaRKWWdk10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rb9ou/btsKOzp7wdX/DjBVJ44VTUpjTaRKWWdk10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rb9ou/btsKOzp7wdX/DjBVJ44VTUpjTaRKWWdk10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frb9ou%2FbtsKOzp7wdX%2FDjBVJ44VTUpjTaRKWWdk10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;356&quot; height=&quot;70&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BYA8j/btsKPOfgqGd/QABtF3Knaj4lkB2YrOKt20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BYA8j/btsKPOfgqGd/QABtF3Knaj4lkB2YrOKt20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BYA8j/btsKPOfgqGd/QABtF3Knaj4lkB2YrOKt20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBYA8j%2FbtsKPOfgqGd%2FQABtF3Knaj4lkB2YrOKt20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;352&quot; height=&quot;67&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;loop&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust는 while문이 존재함에도, 무한 루프를 정의하기 위한 &quot;loop&quot;가 따로 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1732018942179&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fn main() {
    let mut count = 0;

    let result = loop {
        count += 1;

        if count == 10 {
            break count * 2; // 루프를 종료하면서 값을 반환합니다.
        }
    };

    println!(&quot;The result is: {}&quot;, result);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특이한 점은 while/loop문 역시 동적 변수 할당이 가능하다는 점이다. loop를 돌다가 break하는 시점에 &quot;result&quot; 변수를 할당해 줄 수 있다. 리턴하고 싶은 값은 break 바로 뒤어 적어주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnyFlv/btsKOqNwWxe/HtGKlQGzFvx1jN7vVNYY4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnyFlv/btsKOqNwWxe/HtGKlQGzFvx1jN7vVNYY4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnyFlv/btsKOqNwWxe/HtGKlQGzFvx1jN7vVNYY4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnyFlv%2FbtsKOqNwWxe%2FHtGKlQGzFvx1jN7vVNYY4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;870&quot; height=&quot;66&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;66&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;match&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C의 switch/case와 유사한 문법이 Rust에도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1732019628251&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use std::io;

fn main() {
    loop {
        println!(&quot;Guess my favorite natural number:&quot;);
        let mut input = String::new();
        io::stdin().read_line(&amp;amp;mut input)
            .expect(&quot;Failed to read line&quot;);
        let number: i32 = match input.trim().parse() {
            // 예외 처리부
            Ok(num) =&amp;gt; num,
            Err(_) =&amp;gt; {
                println!(&quot;Please type a valid number!&quot;);
                continue;
            }
        };

        match number {
            1 =&amp;gt; println!(&quot;It's my second favorite!&quot;),
            2 | 3 | 5 | 7 | 11 =&amp;gt; println!(&quot;This is a prime number, I love prime numbers&quot;),
            13 =&amp;gt; {
                println!(&quot;This is my favorite number!&quot;);
                break;
            },
            _ =&amp;gt; println!(&quot;This is just a number.&quot;),
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 작성하면, 13을 맞힐 때까지 반복해서 숫자를 입력해야하는 무한 루프를 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732020192197&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use std::io;

fn main() {
    println!(&quot;How old are you?:&quot;);
        let mut input = String::new();
        io::stdin().read_line(&amp;amp;mut input)
            .expect(&quot;Failed to read line&quot;);
        let age: i32 = match input.trim().parse() {
            // 예외 처리부
            Ok(num) =&amp;gt; num,
            Err(_) =&amp;gt; {
                println!(&quot;Please type a valid age!&quot;);
                return;
            }
        };

    let group = match age {
        0..=10 =&amp;gt; &quot;child&quot;,
        11..=20 =&amp;gt; &quot;teenager&quot;,
        21..=64 =&amp;gt; &quot;adult&quot;,
        _ =&amp;gt; &quot;senior&quot;,
    };

    println!(&quot;You are in the {} group.&quot;, group);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 구성하면, &quot;age&quot;가 어떤 구간에 속하냐에 따라 조건 분기가 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yyaSy/btsKOkGLPrB/gd9S1CB9LRqmrFZHpF63ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yyaSy/btsKOkGLPrB/gd9S1CB9LRqmrFZHpF63ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yyaSy/btsKOkGLPrB/gd9S1CB9LRqmrFZHpF63ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyyaSy%2FbtsKOkGLPrB%2Fgd9S1CB9LRqmrFZHpF63ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;878&quot; height=&quot;92&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음이라 좀 난해한 구석이 있지만.. 적응하고 나면 정말 가독성이 좋은 언어일 것 같다.&lt;/p&gt;</description>
      <category>AI/Rust</category>
      <category>letif</category>
      <category>Loop</category>
      <category>Match</category>
      <category>Rust</category>
      <category>반복문</category>
      <category>조건문</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/503</guid>
      <comments>https://mingchin.tistory.com/503#entry503comment</comments>
      <pubDate>Tue, 19 Nov 2024 21:45:06 +0900</pubDate>
    </item>
    <item>
      <title>[Git/Gitea] Gitea Actions를 활용한 테스트 코드 실행 자동화 (2)</title>
      <link>https://mingchin.tistory.com/502</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mingchin.tistory.com/499&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에 이어서.. 시행착오를 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Docker Compose를 기반 act_runner 활성화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서는 act_runner binary를 다운 받고, 이를 직접 실행해 daemon을 활성화 하는 방식으로 Gitea와 연결해 테스트 코드를 실행했다. 하지만 이렇게 하면 매우 기본적인 실행들 외에는 많은 제약이 발생하고, 커스터마이징 하기가 쉽지 않다. (하나만 띄울 수 있고, 직접 daemon을 매번 재실행해야 하며, 관련 로그도 보기 어렵다 등등) 따라서 Docker compose를 활용해 act_runner를 컨테이너로 실행하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Act_runner 설정 config.yaml 준비&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1732000813278&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;act_runner generate-config &amp;gt; config.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행하여, confing.yaml을 생성하면 정말 많은 옵션들을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분은 수정하지 않고 그냥 사용해도 무방하지만, 몇 가지 살펴볼 것들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;runner - labels&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732001007117&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;runner:
  labels: &quot;label_name&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 많이 헷갈렸는데, 그냥 내가 띄우고자 하는 act_runner의 별명을 지어준다고 생각하면 된다. 레이블을 활용해 특정 동작을 선택적으로 연결하고 실행할 수 있다. 여기 지정하는 값은, 실제 workflow 관련 yml 파일이나 이어 작성할 docker-compose.yml 내에도 &lt;b&gt;연결하기 위해 동일하게 세팅&lt;/b&gt;해줘야 하는 값이므로 기억해두자.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1. Runner 분류: 여러 Runner를 운영할 때, 특정 Runner에만 워크플로를 실행시키고 싶을 때 사용합니다. 예를 들어, 테스트 환경용 Runner와 프로덕션 환경용 Runner를 분리하여 사용할 수 있습니다.&lt;br /&gt;2. 작업 필터링: label은 runs-on 필드와 매칭되어 특정 Runner에서만 작업이 실행되도록 합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;runner - envs - DOCKER_REGISTRY_AUTH&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732001521976&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;runner:
  DOCKER_REGISTRY_AUTH: &quot;~/.docker/config.json&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;온프로미스 레지스트리 내 이미지를 활용해 Gitea Actions를 수행하고 싶은 경우 셋팅해줘야 한다. 이미 구축한 온프로미스 레지스트리에 로그인을 수행하게 되면 해당 경로에 자동으로 파일이 생성되며, 그 안에는 아래와 같은 형식의 텍스트가 저장돼 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732001652853&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
        &quot;auths&quot;: {
                &quot;my_ip:my_port&quot;: {
                        &quot;auth&quot;: &quot;MyAuThCodE=&quot;
                }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Action을 수행하는 순간 도커 이미지를 pull하고 환경을 구축하도록 할 예정이기 때문에 필요한 부분!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;container-&lt;span&gt; valid_volumes&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732002224997&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;container:
  # If you want to allow any volume, please use the following configuration:
  valid_volumes:
    - '**'
  # valid_volumes: []&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Actions를 수행할 컨테이너가 특정 경로를 볼륨으로 잡고 싶은 경우 설정한다. 주석에서 알 수 있듯 어떤 볼륨이든 따로 인자를 주어 잡을 수 있게 하려면 위의 옵션을, 그렇지 않으면 리스트에 직접 볼륨 잡을 목록들을 넣으면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;container-&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;valid_volumes&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732002364857&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;container:
  # If it's empty, /workspace will be used.
  workdir_parent:&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Actions를 수행할 컨테이너의 진입 working directory를 지정할 수 있다. 어차피 특정 레포지토리를 clone해서 사용할 것이라 나의 경우는 사용할 필요가 없었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;docker-compose.yml 준비&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1732002564196&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3'

services:
  act_runner:
    image: gitea/act_runner:latest
    container_name: gitea_act_runner
    privileged: true
    environment:
      CONFIG_FILE: /config.yaml
      GITEA_INSTANCE_URL: my_url # Gitea 서버 URL로 수정
      GITEA_RUNNER_REGISTRATION_TOKEN: MY_TOKEN_HASH # Gitea에서 생성된 액트 러너 토큰으로 수정
      GITEA_RUNNER_LABELS: label_name # 러너에 적용할 레이블 설정
      GITEA_RUNNER_NAME: runner_name # 러너 이름 설정
      GITEA_RUNNER_CAPACITY: 1 # 동시에 처리할 작업 수 (1은 기본값)
    restart: always
    volumes:
      # .runner file을 직접 보고 싶은 경우에만 활성화
      # - ./act_runner/data:/data
      - ./config.yaml:/config.yaml
      # 컨테이너 내에서 로컬 docker.sock을 사용하고 싶은 경우
      - /var/run/docker.sock:/var/run/docker.sock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 셋팅은 위와 같다. GITEA_RUNNER_REGISTRATION_TOKEN의 경우 지난 글과 동일한 방식으로 받아 오면 되고, GITEA_RUNNER_LABELS는 위의 runners - label 에서 지정한 &quot;레이블 명&quot;과 동일한 값을 사용해야 연결된다. volumes에 위에서 작성한 config.yaml을 act_runner 컨테이너가 바라볼 수 있도록 해준다. (아래와 같이 같은 depth에 존재할 때 기준이며, 다른 위치라면 경로에 맞게 수정)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;41&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vuulZ/btsKOl6rwd7/0oaz2q7OIEGx1qHsoBf5y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vuulZ/btsKOl6rwd7/0oaz2q7OIEGx1qHsoBf5y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vuulZ/btsKOl6rwd7/0oaz2q7OIEGx1qHsoBf5y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvuulZ%2FbtsKOl6rwd7%2F0oaz2q7OIEGx1qHsoBf5y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;245&quot; height=&quot;41&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;41&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732002977087&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# act_runner 실행
docker compose up -d

# 컨테이너 로그 확인
docker logs -f gitea_act_runner

# act_runner 내리기
docker compose down&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련해 문제가 발생하면 logs를 통해 확인할 수 있다. Gitea GUI의 Settings - Actions - Runners 에서 &quot;idle&quot;로 활성화 된 runner가 있어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;142&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEvF0q/btsKOdN8Eot/flkDCkhrx0N1gNIGekmrB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEvF0q/btsKOdN8Eot/flkDCkhrx0N1gNIGekmrB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEvF0q/btsKOdN8Eot/flkDCkhrx0N1gNIGekmrB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEvF0q%2FbtsKOdN8Eot%2FflkDCkhrx0N1gNIGekmrB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;142&quot; height=&quot;73&quot; data-origin-width=&quot;142&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Workflow 실행을 위한 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gitea는 기본적으로 ubuntu:latest 이미지를 활용해 테스트를 수행하지만, 나는 온프로미스에 따로 구축해둔 &quot;my_image&quot;를 활용해 테스트를 수행해야 하는 상황이었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732003334301&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Test Workflow

on:
  push:
    branches:
      - feat/gitea-actions

jobs:
  test:
    runs-on: label_name
    container:
      image: my_image:latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2  # 레포지토리의 변경 사항을 가져옴
        token: ${{ secrets.PAT }}
      - name: AdaptModel
        run: python builder.py&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 샘플 코드를 실행하기 위해 필요한 단계는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 테스트 용 이미지 내 필수 패키지 설치(Node.js, git)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 구성하다보면 layer를 최소화 하다보니 일반적으로 있을만한 패키지들이 자주 누락된다. 위의 steps 중 &quot;Checkout code&quot;에 해당하는 부분은, git으로 Gitea 레포지토리에 접근하여 현 브랜치(feat/gitea-actions)의 변경 사항을 clone 해오는 역할을 한다. 이는 Github Actions의 호환 런타임인데, Node.js를 필요로 한다. 따라서 두 패키지를 이미지 내에서 활용 가능하도록 설치해주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1732003739519&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Install Node.js &amp;amp; git (minimal stage)
RUN curl -fsSL https://rpm.nodesource.com/setup_16.x | bash - &amp;amp;&amp;amp; \
    dnf install -y nodejs git  &amp;amp;&amp;amp; \
    dnf clean all&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2) git clone을 위한 토큰 설정&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 clone 과정을 위해, Gitea에 로그인하는 과정이 필요할 수 있다. ID/Passwd로 레포지토리에 대한 접근을 허용하는 경우, Settings - Actions - Secrets에 이를 사전 지정하여 코드에 ID/Passwd를 노출하지 않고 안전하게 접근할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W6FrB/btsKOnb5Blb/7ZvwufP3phtDLM7pAslElk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W6FrB/btsKOnb5Blb/7ZvwufP3phtDLM7pAslElk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W6FrB/btsKOnb5Blb/7ZvwufP3phtDLM7pAslElk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW6FrB%2FbtsKOnb5Blb%2F7ZvwufP3phtDLM7pAslElk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;257&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;username:password&quot; 형식&lt;/b&gt;으로 입력해두고, token: ${{ secrets.PAT }} 를 추가하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3) 실행 코드 넣기&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 진행하면, 작업 브랜치(feat/gitea-actions)에 push를 하는 순간 &quot;my_image:latest&quot;를 이용해 컨테이너를 띄우고, gitea 레포지토리에 사전 정의된 ID/Passwd로 로그인 후 clone을 수행해 작업 브랜치와 동일한 소스코드를 가진다. 따라서 해당 소스코드에 대해 테스트 하고 싶은 내용들을 순차적으로 &quot;name/run&quot;의 쌍으로 넣어주면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때 컨테이너 내에서 컨테이너를 띄우는 것이 물리적으로 가능은 하지만.. 기본적으로 각 &quot;name/run&quot; 쌍의 프로세스가 정상 종료되었는지 여부로 Actions 탭에서 성공 실패를 띄워주기 때문에, 이 부분을 유의해서 각 단계를 설정해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRmyXp/btsKPv1iU0H/24xN2COppJv3ndS9DylMmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRmyXp/btsKPv1iU0H/24xN2COppJv3ndS9DylMmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRmyXp/btsKPv1iU0H/24xN2COppJv3ndS9DylMmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRmyXp%2FbtsKPv1iU0H%2F24xN2COppJv3ndS9DylMmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;902&quot; height=&quot;303&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 번의 시행착오 끝에..! 이제 정말 유효한 테스트 코드들을 구성하고, 작업마다 정상 동작하는지 여부를 체크하도록 하는 일만 남았다.&lt;/p&gt;</description>
      <category>IT 역량/Git, Github</category>
      <category>ci/cd</category>
      <category>gitea actions</category>
      <category>오블완</category>
      <category>테스트 자동화</category>
      <category>티스토리챌린지</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/502</guid>
      <comments>https://mingchin.tistory.com/502#entry502comment</comments>
      <pubDate>Tue, 19 Nov 2024 17:19:32 +0900</pubDate>
    </item>
    <item>
      <title>[Rust] 소유권과 라이프타임을 통한 메모리 관리</title>
      <link>https://mingchin.tistory.com/501</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Rust가 가지는 특별한 개념인 소유권에 대해 Copilot에게 물었을 때 아래의 답을 얻을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;소유권&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Rust의 소유권 시스템은 메모리 안전성을 보장하기 위해 설계되었습니다. 주요 개념은 다음과 같습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;소유권(Ownership)&lt;/b&gt;: 각 값은 특정 변수에 소유권이 있습니다. &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;한 번에 하나의 변수만 값을 소유&lt;/b&gt;&lt;/span&gt;할 수 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;이동(Move)&lt;/b&gt;: &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;소유권이 다른 변수로 이동&lt;/b&gt;&lt;/span&gt;할 수 있습니다. 이동 후 원래 변수는 더 이상 값을 사용할 수 없습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;빌림(Borrowing)&lt;/b&gt;: &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;소유권을 이동하지 않고 값을 참조&lt;/b&gt;&lt;/span&gt;할 수 있습니다. 빌림에는 불변 참조와 가변 참조가 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;불변 참조(&amp;amp;)&lt;/b&gt;: 값을 읽기만 할 수 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;가변 참조(&amp;amp;mut)&lt;/b&gt;: 값을 변경할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;라이프타임(Lifetime)&lt;/b&gt;: 참조가 유효한 범위를 나타냅니다. Rust는 라이프타임을 추적하여 유효하지 않은 참조를 방지합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이러한 소유권은, C 계열 언어의 어떤 문제를 극복하고자 설계된 개념일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;1. 메모리 누수&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;C와 C++에서는 프로그래머가 &lt;b&gt;직접 메모리를 할당하고 해제&lt;/b&gt;해야 합니다. 이 과정에서 메모리를 제대로 해제하지 않으면 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;메모리 누수&lt;/b&gt;&lt;/span&gt;가 발생할 수 있습니다. Rust는 소유권 시스템을 통해 메모리를 자동으로 관리하여 이러한 문제를 방지합니다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;2. 잘못된 메모리 접근&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;C 계열 언어에서는 포인터를 통해 &lt;b&gt;메모리 주소를 직접 조작&lt;/b&gt;할 수 있습니다. 이로 인해 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;잘못된 메모리 접근&lt;/b&gt;&lt;/span&gt;(예: 해제된 메모리 접근, 경계 밖 메모리 접근 등)이 발생할 수 있습니다. Rust는 소유권과 빌림 규칙을 통해 이러한 잘못된 접근을 컴파일 타임에 방지합니다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;3. 데이터 경합&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;멀티스레드 환경에서 &lt;b&gt;여러 스레드가 동시에 동일한 메모리에 접근&lt;/b&gt;할 때 데이터 경합이 발생할 수 있습니다. C와 C++에서는 이러한 문제를 해결하기 위해 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;복잡한 동기화 메커니즘&lt;/b&gt;&lt;/span&gt;을 사용해야 합니다. Rust는 소유권과 빌림을 통해 데이터 경합을 방지하고, 안전한 동시성을 보장합니다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;4. 안전하지 않은 코드&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;C와 C++에서는 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;메모리 안전성을 보장하지 않는 코드&lt;/b&gt;&lt;/span&gt;가 쉽게 작성될 수 있습니다. Rust는 소유권 시스템을 통해 메모리 안전성을 보장하며, 안전하지 않은 코드를 작성할 때는 명시적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;unsafe&lt;span&gt;&amp;nbsp;&lt;/span&gt;블록을 사용하도록 요구합니다. 이를 통해 안전하지 않은 코드의 사용을 최소화하고, 코드의 안전성을 높입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;요약하자면, 직접 메모리를 할당하고 해제하거나 메모리 주소를 조작하는 등 개발자에게 많은 권한이 주어지기 때문에 반대로 개발자의 실수나 부주의로 인한 문제가 발생할 위험이 커진다. 따라서 Rust는 소유권 등의 개념에 기반하여 이를 원천적으로 제한하여 위험을 감소시키고자 한다. 생성형 AI 기반의 코드 작성이 피할 수 없는 흐름인 시대가 오는 만큼 더더욱 이런 언어 차원에서의 안정성 확보가 좋은 안전장치가 되어줄 것이라고 생각하고, 그래서 Rust가 매력적이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731829316957&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// C 코드에서 발생할 수 있는 문제 - 해제된 메모리에 대한 접근
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

void use_after_free() {
    int *ptr = (int *)malloc(sizeof(int));
    *ptr = 42;
    free(ptr);
    printf(&quot;%d\n&quot;, *ptr); // 해제된 메모리에 접근 -&amp;gt; 정의되지 않은 동작이며 Rust에서는 이와 같은 접근이 불가
}

int main() {
    use_after_free();
    return 0;
}

// Rust에서의 안정성
fn use_after_free() {
    let ptr = Box::new(42); // Box는 힙에 메모리를 할당하고 소유권을 가집니다.
    // 메모리는 ptr이 범위를 벗어날 때 자동으로 해제됩니다.
    println!(&quot;{}&quot;, *ptr); // 안전하게 접근
}

fn main() {
    use_after_free();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;예시 코드로 소유권 이해하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새롭게 &quot;onwership&quot;이라는 프로젝트를 생성하고, main.rs에 아래와 같이 작성해 실행해보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1731829668038&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cargo new onwership&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1731829680735&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fn main() {
    let s1 = String::from(&quot;Hello&quot;); // s1이 문자열 &quot;Hello&quot;의 소유자입니다.
    let s2 = s1; // 소유권이 s1에서 s2로 이동합니다. 이제 s1은 더 이상 사용할 수 없습니다.

    // println!(&quot;{}&quot;, s1); // 이 줄은 컴파일되지 않습니다. s1은 더 이상 유효하지 않습니다.
    println!(&quot;{}&quot;, s2); // s2는 &quot;Hello&quot;를 출력합니다.

    let s3 = String::from(&quot;World&quot;);
    let s4 = &amp;amp;s3; // s3의 불변 참조를 s4에 빌려줍니다.
    println!(&quot;{}&quot;, s3); // s3는 여전히 유효합니다.
    println!(&quot;{}&quot;, s4); // s4는 &quot;World&quot;를 출력합니다.

    let mut s5 = String::from(&quot;Rust&quot;);
    let s6 = &amp;amp;mut s5; // s5의 가변 참조를 s6에 빌려줍니다.
    s6.push_str(&quot; Programming&quot;); // s6를 통해 s5를 변경할 수 있습니다.
    println!(&quot;{}&quot;, s5); // s5는 &quot;Rust Programming&quot;을 출력합니다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnGx2f/btsKMEK5RTt/b165WFtdK0QCEdBO6WKTY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnGx2f/btsKMEK5RTt/b165WFtdK0QCEdBO6WKTY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnGx2f/btsKMEK5RTt/b165WFtdK0QCEdBO6WKTY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnGx2f%2FbtsKMEK5RTt%2Fb165WFtdK0QCEdBO6WKTY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;876&quot; height=&quot;137&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;s1을 &lt;b&gt;정의하는 순간 생긴 소유권&lt;/b&gt;이, s2를 정의하는 순간 s1으로부터 s2로 옮겨갔다(&lt;b&gt;소유권 이동&lt;/b&gt;). 따라서 println!(&quot;{}&quot;, s1); 과 같은 코드는 동작할 수 없다. 실제 해당 주석을 해제하고 컴파일을 시도해보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLPtDY/btsKMH1Vrz0/bBoSdKUC1nN6VmQwfD4TOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLPtDY/btsKMH1Vrz0/bBoSdKUC1nN6VmQwfD4TOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLPtDY/btsKMH1Vrz0/bBoSdKUC1nN6VmQwfD4TOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLPtDY%2FbtsKMH1Vrz0%2FbBoSdKUC1nN6VmQwfD4TOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1480&quot; height=&quot;457&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 친절하게 에러를 뱉어준다. (물론 이미 extension이 설치된 VSCode가 알려준다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소유권을 이전 받지 않고 값을 참조하는 방법에는 불변 참조(&amp;amp;)와 가변 참조(&amp;amp;mut)가 있으며 가변 참조로 받아왔을 때만 s6처럼 본인이 소유권을 가지지 않았음에도 s5를 변경할 수 있다. (여전히 소유권 자체는 s5에 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 예시로는 라이프타임의 개념을 이해할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1731830579240&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fn main() {
    let string1 = String::from(&quot;Hello, world!&quot;);
    let result;
    {
        let string2 = String::from(&quot;Rust&quot;);
        result = longest(&amp;amp;string1, &amp;amp;string2);
        // string2는 이 블록을 벗어나면 소멸됩니다.
        println!(&quot;A: The longest string is {}&quot;, result); // 이 줄은 컴파일되지 않습니다.
    }
    // println!(&quot;B: The longest string is {}&quot;, result); // 이 줄은 컴파일되지 않습니다.
}

// 두 문자열 참조를 받아서 더 긴 문자열 참조를 반환하는 함수
fn longest&amp;lt;'a&amp;gt;(x: &amp;amp;'a str, y: &amp;amp;'a str) -&amp;gt; &amp;amp;'a str {
    if x.len() &amp;gt; y.len() {
        x
    } else {
        y
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드를 실행하면 아래와 같이 출력된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MKkuB/btsKMJSWaxT/LBpMIp4ghFpo3vEknPbfmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MKkuB/btsKMJSWaxT/LBpMIp4ghFpo3vEknPbfmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MKkuB/btsKMJSWaxT/LBpMIp4ghFpo3vEknPbfmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMKkuB%2FbtsKMJSWaxT%2FLBpMIp4ghFpo3vEknPbfmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;877&quot; height=&quot;68&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 A: 로 시작하는 출력문 주석하고 B: 로 시작하는 출력문을 주석 해제하여 컴파일을 시도하면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNaUyX/btsKLgEusoG/AWF4BsgiAWDZZsS6DK0gKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNaUyX/btsKLgEusoG/AWF4BsgiAWDZZsS6DK0gKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNaUyX/btsKLgEusoG/AWF4BsgiAWDZZsS6DK0gKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNaUyX%2FbtsKLgEusoG%2FAWF4BsgiAWDZZsS6DK0gKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1120&quot; height=&quot;327&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 에러를 만날 수 있다. 불변 참조(&amp;amp;)로 빌려온 값의 라이프타임이 해당 참조가 이루어진 블록 밖에서는 소멸되어, 출력할 수 없는 것이다. 하나의 함수 내에서도 블록 단위로 수명을 제한할 수 있다는 것이 놀랍다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust 컴파일러는 라이프타임을 프로그래머가 명시적으로 지정할 수 있으며, longest 함수 내 'a 관련 표현이 &quot;lifetime specifier&quot;에 해당한다. a일 필요는 없으며, 원하는 이름을 자유롭게 쓸 수 있다고 한다. ('abc 와 같이 사용 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한 라이프타임 명시는 아래의 경우에는 생략될 수 있다고 하는데, &quot;규칙에 맞게 작성하면 생략할 수 있다&quot; 정도만 기억하고 보다 상세한 이해는 차차 해나가도록 하자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #242424; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;입력 참조가 하나인 경우&lt;/b&gt;: 입력 참조의 라이프타임이 반환 참조의 라이프타임과 동일합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;여러 입력 참조가 있는 경우&lt;/b&gt;: 반환 참조의 라이프타임은 첫 번째 입력 참조의 라이프타임과 동일합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;메서드의 경우&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;self의 라이프타임이 반환 참조의 라이프타임과 동일합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 1에 해당하는&amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: left;&quot;&gt;입력 참조와 반환 참조의 라이프타임이 동일하다는 것을 컴파일러가 추론할 수 있는 예시이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731831471378&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fn first_word(s: &amp;amp;str) -&amp;gt; &amp;amp;str {
    let bytes = s.as_bytes();
    for (i, &amp;amp;item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &amp;amp;s[0..i];
        }
    }
    &amp;amp;s[..]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/Rust</category>
      <category>Rust</category>
      <category>라이프타임</category>
      <category>소유권</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/501</guid>
      <comments>https://mingchin.tistory.com/501#entry501comment</comments>
      <pubDate>Sun, 17 Nov 2024 17:18:16 +0900</pubDate>
    </item>
    <item>
      <title>[Rust] cargo 프로젝트 내 Cargo.toml과 Cargo.lock 그리고 incremental compilation</title>
      <link>https://mingchin.tistory.com/500</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 첫 글에서 hello Rust 프로젝트를 생성하고, 이를 컴파일 및 실행하면서 자동으로 생긴 리소스들 중 관심이 가는 것들을 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Cargo.toml&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Copilot에게 Cargo.toml에 대해 물으니 아래와 같은 답변을 받을 수 있었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Cargo.toml 파일은 Rust 프로젝트의 설정 파일로, 프로젝트의 메타데이터와 의존성을 관리하는 중요한 역할을 합니다. 이 파일은 TOML (Tom&amp;rsquo;s Obvious, Minimal Language) 형식으로 작성되며, 다음과 같은 주요 섹션으로 구성됩니다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731591896061&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[package]
name = &quot;my_project&quot;
version = &quot;0.1.0&quot;
authors = [&quot;Your Name &amp;lt;your.email@example.com&amp;gt;&quot;]
edition = &quot;2021&quot;

[dependencies]
my_other_project = &quot;0.2.0&quot;
serde = &quot;1.0&quot;

[dev-dependencies]
anyhow = &quot;1.0&quot;  # 에러 핸들링을 위한 라이브러리
serde_json = &quot;1.0&quot;  # JSON 파싱을 위한 라이브러리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까.. Rust는 cargo라는 패키지 매니저이자 빌드 시스템을 활용해 &quot;cargo build&quot;, &quot;cargo publish&quot; 등 빌드와 배포를 진행할 수 있다. 이때 Cargo.toml 파일은 해당 프로젝트의 버전, 타 프로젝트와의 의존성, &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;개발 중에만 필요한 라이브러리의 의존성을 명시할 수 있다.&amp;nbsp; [dev-dependencies] 섹션에 명시된 라이브러리들은, 개발 중에만 사용되고 배포 빌드에는 포함되지 않는다고 한다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;또한 명시된 프로젝트 버전 정보가 빌드된 파일의 메타데이터에 포함돼, 디버깅이나 배포에 편리하다. 버전 및 의존성 관리를 강제하는..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Cargo.lock&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cargo.lock&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일은 프로젝트의 의존성 트리를 고정(freeze)하는 역할을 한다. Cargo.toml&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일에 명시된 의존성뿐만 아니라, 그 의존성들이 의존하는 모든 하위 의존성들의 정확한 버전을 기록하여, 동일한 빌드가 재현 가능하도록 한다.&lt;br /&gt;아니 근데 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;Cargo.toml에서 이미 고정했는데...? 라고 생각했으나.. 두 파일의 역할은 조금 다르고, 상호 보완적이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;Cargo.toml의 역할&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #111111; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의존성 선언&lt;/b&gt;: 프로젝트에서 필요한 라이브러리와 그 &lt;b&gt;버전 범위를 선언&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사양&lt;/b&gt;: 어떤 라이브러리가 필요한지, 그리고 그 라이브러리의 &lt;b&gt;최소 버전 요구 사항을 명시&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;Cargo.lock의 역할&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #111111; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의존성 고정&lt;/b&gt;: 실제로 사용된 라이브러리의 &lt;b&gt;정확한 버전을 기록&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재현 가능성&lt;/b&gt;: 동일한 프로젝트를 다른 환경에서 빌드할 때, &lt;b&gt;동일한 의존성 버전을 사용하도록 보장&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;Cargo.toml에는 필수 및 최소 의존성을 표기한다면, Cargo.lock은 언제든 특정 빌드를 재현할 수 있는 안전장치다. 실제 여러 명의 개발자가 협업하는 상황에서는 환경이 꼬이는 일이 허다하기 때문에..&amp;nbsp;&lt;br /&gt;예를 들어 위 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;Cargo.toml&lt;/span&gt; 파일 예시의 serde = &quot;1.0&quot;를 보면, 개발자는 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;1.0.x 버전 중 어떤 것이든 사용할 수 있다. 다만 특정 시점에 빌드를 하고 배포할 때 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;1.0.130 버전이 사용됐다면, 이는 Cargo.lock에 기록하여 오류 및 버그 가능성을 줄인다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731593420575&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cargo update&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;위 명령어를 통해, &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;Cargo.toml&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일에 기록된 의존성의 범위 내에서 가능한 최신 버전으로 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;Cargo.lock을 업데이트 할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199; color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;color: #111111; text-align: left;&quot;&gt;증분 컴파일(Incremental Compilation)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #111111;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;프로젝트 내 target/debug/incremental 디렉토리를 보면, 소스코드 변경 후 빌드를 반복할 때마다 무언가 파일이 쌓인다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7Uxbf/btsKJPFVokr/nK083Zl2dfpZIv71WFKZPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7Uxbf/btsKJPFVokr/nK083Zl2dfpZIv71WFKZPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7Uxbf/btsKJPFVokr/nK083Zl2dfpZIv71WFKZPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7Uxbf%2FbtsKJPFVokr%2FnK083Zl2dfpZIv71WFKZPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;897&quot; height=&quot;131&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1177&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JiypC/btsKKhIOtGc/zxXGBVDV0jkVZTSK01cMl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JiypC/btsKKhIOtGc/zxXGBVDV0jkVZTSK01cMl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JiypC/btsKKhIOtGc/zxXGBVDV0jkVZTSK01cMl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJiypC%2FbtsKKhIOtGc%2FzxXGBVDV0jkVZTSK01cMl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1177&quot; height=&quot;115&quot; data-origin-width=&quot;1177&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnzfGQ/btsKIvu7Imm/SicRn2rJ1JDCsm5VpNOsZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnzfGQ/btsKIvu7Imm/SicRn2rJ1JDCsm5VpNOsZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnzfGQ/btsKIvu7Imm/SicRn2rJ1JDCsm5VpNOsZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnzfGQ%2FbtsKIvu7Imm%2FSicRn2rJ1JDCsm5VpNOsZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;838&quot; height=&quot;137&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 안에는 다시 .lock 파일과 bin 파일들이 생성된 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 디렉토리는 &lt;b&gt;증분 컴파일&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;(incremental compilation)과 관련된 데이터를 저장하는 곳이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;증분 컴파일은 Rust 컴파일러가 이전 빌드의 결과를 재사용하여 컴파일 시간을 단축시키는 기능입니다.&amp;nbsp;&lt;br /&gt;이 디렉토리에는 컴파일러가 중간 결과를 캐시하여, 코드의 일부만 변경되었을 때 전체를 다시 컴파일하지 않고 변경된 부분만 다시 컴파일할 수 있도록 합니다&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker가 이미지를 layer로 관리하며 빌드 과정을 캐싱하고 불필요한 메모리 사용과 빌드 과정을 줄이는 것과 유사한 개념인 것 같다. 의존성 그래프를 활용해 변경되지 않은 부분은 재사용하고, 변경된 부분만 다시 컴파일한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 알아서 증분 컴파일 캐시를 제거하여 너무 커지지 않도록 관리하며, &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;소스 코드가 크게 변경되거나 프로젝트 설정이 변경되면, 컴파일러는 캐시를 무효화하고 필요한 부분을 다시 컴파일하여 오래된 데이터가 남아 있지 않도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731593995825&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cargo clean&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 위 명령어로 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;아티팩트와 증분 컴파일 캐시를 모두 삭제할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;다음 글에는 기본 문법과 Rust 만의 특별한 문법이 있다면 공부해보자.&lt;/span&gt;&lt;/p&gt;</description>
      <category>AI/Rust</category>
      <category>Rust</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/500</guid>
      <comments>https://mingchin.tistory.com/500#entry500comment</comments>
      <pubDate>Thu, 14 Nov 2024 23:21:05 +0900</pubDate>
    </item>
    <item>
      <title>[Git/Gitea] Gitea Actions를 활용한 테스트 코드 실행 자동화 (1)</title>
      <link>https://mingchin.tistory.com/499</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온프로미스 구축이 용이하다는 점 때문에, Gitea를 도커 컨테이너로 띄워놓고 운영 중이다. Github actions과 유사한 기능을 Gitea에서도 사용할 수 있는데, 환경 구축과 테스트 코드 실행 과정을 정리한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXmpCX/btsKIelp10r/PE5ZpZHkK4fTO7gCj3TmDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXmpCX/btsKIelp10r/PE5ZpZHkK4fTO7gCj3TmDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXmpCX/btsKIelp10r/PE5ZpZHkK4fTO7gCj3TmDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXmpCX%2FbtsKIelp10r%2FPE5ZpZHkK4fTO7gCj3TmDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;310&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 구축&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) Gitea Actions 활성화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gitea 서버가 떠있는 공간에서 설정을 바꿔준다. 기본적으로는 actions 탭이 활성화 돼있지 않다.&lt;/p&gt;
&lt;pre id=&quot;code_1731545449972&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# custom/conf or config 디렉토리 내 app.ini 에 추가
[actions]
ENABLED = true
DEFAULT_ACTIONS_URL = https://gitea.com/gitea/act_runner

# 설정 추가 후
systemctl restart gitea

# 혹은
docker restart {gitea_container]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Gitea GUI 환경에서 각 repo 마다 settings Actions 탭을 활성화 할 수 있다. (이 부분 역시 모든 레포에 대해 기본적으로 Actions 탭을 활성화 하도록 환경 셋팅을 바꿀 수 있다고 한다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AMxo0/btsKH5WCr3R/jrhGUKfOvpiotwIi6Z7PY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AMxo0/btsKH5WCr3R/jrhGUKfOvpiotwIi6Z7PY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AMxo0/btsKH5WCr3R/jrhGUKfOvpiotwIi6Z7PY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAMxo0%2FbtsKH5WCr3R%2FjrhGUKfOvpiotwIi6Z7PY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;379&quot; height=&quot;162&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 워크플로 파일 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동으로 실행하고 싶은 작업을 생성한다. 레포지토리 루트에 &quot;.gitea/workflow&quot; 디렉토리를 생성 후, &lt;b&gt;&quot;.yml&quot; 확장자&lt;/b&gt;로 아래와 같이 추가하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1731545884972&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir -p .gitea/workflows

# .gitea/workflows/test.yml 생성
name: Test Workflow

on:
  push:
    branches:
      - main
    paths:
      - 'builder/**'

jobs:
  test:
    runs-on: self-hosted
    steps:
      - name: Run tests
        run: echo HI&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;on&quot;은 트리거를 의미한다. 위 예시는 main branch에 push하는 순간 자동으로 해당 workflow가 실행되도록 설정한 것이며, 외에도 pull_request, schedule(정기 실행), workflow_dispatch(수동 실행), release 등의 옵션이 있다. 또한 paths를 지정한 경우에는, 해당 부분에 변경 사항이 있을 때만 실행되도록 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;jobs&quot;는 실제 실행한 동작과 관계된다. &quot;runs-on&quot;의 경우, 해당 동작을 실행할 컨테이너를 띄울 이미지를 설정할 수 있다. 기본값으로는 ubuntu:latest 이미지가 활용되는 것으로 보이며, 커스터마이징도 가능하다. &quot;self-hosted&quot;는 별도 컨테이너를 띄우지 않고 push 등 동작이 발생한 로컬 환경에서 action을 수행하도록 한다. &quot;steps&quot; 하위에 여러 개의 &quot;name&quot;과 &quot;run&quot;을 순차적으로 동작하도록 배치가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) act_runner 설치 및 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gtiea는 &quot;act_runner&quot;를 통해 사전 정의된 action을 실행하고, 그 결과를 Gitea 서버에 전달해 출력한다. 이 부분은 별도 설치 및 셋팅이 필요하다. &lt;a href=&quot;https://dl.gitea.com/act_runner/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;에서 OS에 맞는 버전을 선택해 다운로드 받으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731546806226&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 리눅스
wget https://dl.gitea.com/act_runner/0.2.11/act_runner-0.2.11-linux-amd64

# 실행 권한
chmod +x act_runner-0.2.11-linux-amd64

# bin 폴더로 이동
mv act_runner-0.2.11-linux-amd64 /usr/local/bin/act_runner&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 bin 디렉토리로 옮겨 놓으면 편하게 사용이 가능하다. 이제 이 act_runner를 통해 daemon을 직접 실행하거나, 혹은 config를 별도로 작성 후 docker-compose를 통해 act_runner를 실행할 수 있는데, 일단 테스트를 위해 전자로 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731547347705&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;act_runner register&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 실행하면, interactive하게 기본 설정들을 할 수 있다. (분명 --instance 등의 인자를 줄 수 있게 돼있는데, 줘도 안먹고 다시 입력해야 한다.) gitea 서버 주소, token, tag_name 등을 순차적으로 입력하면 된다. token의 경우 연결하고자 하는 레포지토리의 settings - Actions - runners 탭에서 아래와 같이 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mhOnW/btsKH6uqvR7/M4b0tC0NKY9MMeQsmD7QQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mhOnW/btsKH6uqvR7/M4b0tC0NKY9MMeQsmD7QQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mhOnW/btsKH6uqvR7/M4b0tC0NKY9MMeQsmD7QQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmhOnW%2FbtsKH6uqvR7%2FM4b0tC0NKY9MMeQsmD7QQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;230&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 등록 절차를 마칠 수 있고, 이제 이 act_runner가 동작할 수 있도록 daemon을 띄워준다.&lt;/p&gt;
&lt;pre id=&quot;code_1731547553877&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nohup act_runner daemon &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길뚫을 위해 이렇게 수행하긴 했으나.. 매번 daemon 띄우는 것도 일이고, 이렇게 하면 하나의 서버에서 한 개의 레포지토리에만 act_runner를 연결할 수 있으니, 결국 config 만들고 docker-compose를 실행하는 쪽으로 진행해야 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731547644839&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;act_runner generate-config &amp;gt; config.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 기본 구조를 갖춘 config.yaml이 생성되고, 여기에 방금 interactive하게 수행한 기본 값들에 대한 셋팅, 그리고 그 외에도 추가적인 기능들과 관련된 셋팅을 수행할 수 있다. 그리고 이 config를 활용해 docker-compose를 수행할 yaml은 아래의 구조로 생성하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1731547781937&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3&quot;

services:
  act_runner:
    image: gitea/act_runner:0.2.10
    container_name: act_runner
    privileged: true
    environment:
      CONFIG_FILE: /config.yaml
      GITEA_INSTANCE_URL: &quot;https://192.168.56.173:3300&quot; 
      GITEA_RUNNER_REGISTRATION_TOKEN: {token}
      GITEA_RUNNER_NAME: &quot;act_runner&quot;
      GITEA_RUNNER_LABELS: ubuntu-latest:docker
    volumes:
      - ~/act-runner/data:/data
      - ~/act-runner/cache:/root/.cache    
      - ./config.yaml:/config.yaml
      - /var/run/docker.sock:/var/run/docker.sock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분 셋팅하고 실제 실행은 다음 번에 해본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;47&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwhsZ7/btsKHKekLnZ/co2fV5vcvNRCBwWJARdFgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwhsZ7/btsKHKekLnZ/co2fV5vcvNRCBwWJARdFgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwhsZ7/btsKHKekLnZ/co2fV5vcvNRCBwWJARdFgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwhsZ7%2FbtsKHKekLnZ%2Fco2fV5vcvNRCBwWJARdFgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;478&quot; height=&quot;47&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;47&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 이 구조로 셋팅하고 &quot;docker-compose up -d&quot; 를 실행하면 직접 register하고 daemon을 실행한 것과 동일한 결과를 보여줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이렇게 직접 daemon을 잘 실행했다면, main branch에 변경 사항을 push 했을 때 아래와 같이 Actions 탭에서의 수행을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boHxbf/btsKJjTP7x9/zCd97kJbvV5rFiw1uQcee1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boHxbf/btsKJjTP7x9/zCd97kJbvV5rFiw1uQcee1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boHxbf/btsKJjTP7x9/zCd97kJbvV5rFiw1uQcee1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboHxbf%2FbtsKJjTP7x9%2FzCd97kJbvV5rFiw1uQcee1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;869&quot; height=&quot;427&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길뚫 성공..!&lt;/p&gt;</description>
      <category>IT 역량/Git, Github</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/499</guid>
      <comments>https://mingchin.tistory.com/499#entry499comment</comments>
      <pubDate>Thu, 14 Nov 2024 10:36:41 +0900</pubDate>
    </item>
    <item>
      <title>[Rust] 공부를 시작하며</title>
      <link>https://mingchin.tistory.com/498</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;목표 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그에 글을 쓰며 공부한 게 언제인지.. 짬을 내서 퇴근 후에 도커와 쿠버네티스 강의를 들으면서도, 사실 짬을 내 정리하는 게 여간 어려운 일이 아니었다. 뒤쳐지지 않기 위해, 그리고 배우는 재미를 잊지 않기 위해 다시 한 번 시작해야 할 때다. 아래의 목표를 가지고 공부를 해보려 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성형 AI 활용법 익히기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드도, 글도, 브레인스토밍도 이제는 생성형 AI 없이 진행하는 것이 바보다 싶을 정도로 훌륭한 초안 짜기 도구가 되었다고 생각한다. 실제 업무에서도 객체 구조를 잡거나 코드 초안의 작성, 규칙이 있는 변경사항에 대한 일괄 적용 등에 자주 활용하고 있다. 글을 쓰는 데에 Copilot을 사용해보며 chatGPT와의 차이도 느껴보고, 그 한계도 더 많이 체험해보고 싶다. 살재로 글의 뼈대나 초안을 Copilot으로 작성해보거나, 공부해야 하는 키워드를 검색해 보는데도 사용할 예정이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저수준 언어와 친해지기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 주 언어는 Python이다. 주로 모델링을 담당하다 보니 업무는 파이썬과 쉘스크립트만 알아도 충분하지만 C++ 기반인 엔진에 대한 보다 깊은 이해를 필요로 하는 순간이나, 저수준 언어에 대한 기반 지식이 아쉽게 느껴질 때가 있다. C 계열 언어의 한계성을 극복하고자 만들어진 Rust를 공부하면, 나에게 더 효용이 높은 시간이 될 것 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안드로이드 개발을 향해&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온디바이스 STT에 대한 수요도 있고.. AI 모델링을 하더라도 서비스를 위해 필요한 백/프론트의 구성이나 흐름을 이해하는 것이 도움이 되듯이, 저수준 언어로 구현될 수밖에 없는 안드로이드 개발 과정을 대략적으로라도 이해해볼 수 있다면 좋겠다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;왜 Rust인가?&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모질라 재단에서 2010년 공개한 언어인 Rust는, &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;b&gt;C/C++와 동등한 수준의 속도를 유지하면서 안전성과 동시성을 향상&lt;/b&gt;시키는 것을 목표로 설계되었다고 한다. &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;C/C++은 메모리 등 하드웨어를 자유자재로 제어할 수 있다는 장점이 있지만, 그만큼 해당 제어를 잘못 했을 때의 위험성 또한 높다. (Segmentation fault나 메모리 누수는 내가 C 개발자가 아닌데도 맨날 듣는 재앙의 소리다.) 더 엄격한 문법과 규칙에 기반해, 이러한 잠재적 위험을 사전에 제거한다고 한다. 아마도 이를 가능하게 하는 아래의 키워드들을 차차 앞으로 공부해야 할 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Keywords: 소유권, 빌림(Borrowing), 수명(Lifetimes), 트레이트, 제네릭(Generics), 에러 처리(Error Handling)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Hello Rust&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;언어 공부의 국룰은 인사하기. 아래의 과정으로 설치하면 된다. (Window11 환경, VSCode는 별도 설치)&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1) Rust 설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://rustup.rs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;에서 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;rustup-init.exe&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일을 다운로드 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7igjq/btsKF4KpxNi/h3GduckPmkkEn8lHtyoFa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7igjq/btsKF4KpxNi/h3GduckPmkkEn8lHtyoFa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7igjq/btsKF4KpxNi/h3GduckPmkkEn8lHtyoFa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7igjq%2FbtsKF4KpxNi%2Fh3GduckPmkkEn8lHtyoFa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;946&quot; height=&quot;358&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 옵션으로 설치했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cx0nON/btsKG9w9VBX/6PiFf1LcGGo8tLRjvKimJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cx0nON/btsKG9w9VBX/6PiFf1LcGGo8tLRjvKimJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cx0nON/btsKG9w9VBX/6PiFf1LcGGo8tLRjvKimJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcx0nON%2FbtsKG9w9VBX%2F6PiFf1LcGGo8tLRjvKimJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;175&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치하는 데 시간이 걸리니 요 친구를 VSCode에 설치해두자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biN2y0/btsKFeNH9cS/2S9lnLFOfKrnhVPrUbxKu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biN2y0/btsKFeNH9cS/2S9lnLFOfKrnhVPrUbxKu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biN2y0/btsKFeNH9cS/2S9lnLFOfKrnhVPrUbxKu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiN2y0%2FbtsKFeNH9cS%2F2S9lnLFOfKrnhVPrUbxKu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;803&quot; height=&quot;278&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친절도 해라.. 1번으로 설치.&amp;nbsp;cmd에서 아래 명령어를 실행해 버전을 확인 할 수 있으면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1731416874248&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rustc --version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;423&quot; data-origin-height=&quot;25&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sJLOI/btsKGD6zMBm/MotQiteQOfPUpfDvNjboWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sJLOI/btsKGD6zMBm/MotQiteQOfPUpfDvNjboWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sJLOI/btsKGD6zMBm/MotQiteQOfPUpfDvNjboWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsJLOI%2FbtsKGD6zMBm%2FMotQiteQOfPUpfDvNjboWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;423&quot; height=&quot;25&quot; data-origin-width=&quot;423&quot; data-origin-height=&quot;25&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2) Rust 프로젝트 생성 및 실행&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;설치한 &quot;cargo&quot;가 자동으로 프로젝트를 구성해주는데, 프로젝트 이름은 digit으로 시작할 수 없다고 한다. (&lt;s&gt;벌써 빡세다.. 숫자는 외않되?&lt;/s&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zZxMw/btsKFn4Mtxi/NfPW1UdCVAHlNigdwJZYq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zZxMw/btsKFn4Mtxi/NfPW1UdCVAHlNigdwJZYq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zZxMw/btsKFn4Mtxi/NfPW1UdCVAHlNigdwJZYq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzZxMw%2FbtsKFn4Mtxi%2FNfPW1UdCVAHlNigdwJZYq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;43&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1731417149201&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cargo new hello_rust&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kVpgP/btsKFhcCU1l/9zgwdmxv1Q02cPxSHqf7c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kVpgP/btsKFhcCU1l/9zgwdmxv1Q02cPxSHqf7c1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kVpgP/btsKFhcCU1l/9zgwdmxv1Q02cPxSHqf7c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkVpgP%2FbtsKFhcCU1l%2F9zgwdmxv1Q02cPxSHqf7c1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;728&quot; height=&quot;127&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkVMi3/btsKGOmwQFv/RKbj1U41W6UITPUKYBS0j1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkVMi3/btsKGOmwQFv/RKbj1U41W6UITPUKYBS0j1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkVMi3/btsKGOmwQFv/RKbj1U41W6UITPUKYBS0j1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkVMi3%2FbtsKGOmwQFv%2FRKbj1U41W6UITPUKYBS0j1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;83&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들어진 디렉토리 내부를 확인해보면, 기본 구조가 잡혀있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.rs 파일을 열어보면, Hellow, World 출력을 위한 코드가 기본적으로 잡혀있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 아래와 같이 수정해보니,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;87&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KAI6N/btsKHcAF9ze/5eKmpyPMkT0otI2bpOJVZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KAI6N/btsKHcAF9ze/5eKmpyPMkT0otI2bpOJVZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KAI6N/btsKHcAF9ze/5eKmpyPMkT0otI2bpOJVZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKAI6N%2FbtsKHcAF9ze%2F5eKmpyPMkT0otI2bpOJVZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;337&quot; height=&quot;87&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;87&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;191&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGSDmj/btsKHqlamMG/h37W5QLET93Mv8Ao9bG5F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGSDmj/btsKHqlamMG/h37W5QLET93Mv8Ao9bG5F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGSDmj/btsKHqlamMG/h37W5QLET93Mv8Ao9bG5F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGSDmj%2FbtsKHqlamMG%2Fh37W5QLET93Mv8Ao9bG5F1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;191&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;191&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동으로 Cargo.lock 파일이 생성되었다. 이게 어떤 역할을 하는지는 차차 알게 되겠지..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내에서&lt;/p&gt;
&lt;pre id=&quot;code_1731417451879&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cargo run&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행하면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kEvCA/btsKGMvstDG/QXu0oImcWeP4k8VZu4kq30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kEvCA/btsKGMvstDG/QXu0oImcWeP4k8VZu4kq30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kEvCA/btsKGMvstDG/QXu0oImcWeP4k8VZu4kq30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkEvCA%2FbtsKGMvstDG%2FQXu0oImcWeP4k8VZu4kq30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;67&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 후 결과를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프로젝트 내의 변화를 보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XczUV/btsKFeAcoj9/psKSJJUxKEPZ6nFchRV1k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XczUV/btsKFeAcoj9/psKSJJUxKEPZ6nFchRV1k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XczUV/btsKFeAcoj9/psKSJJUxKEPZ6nFchRV1k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXczUV%2FbtsKFeAcoj9%2FpsKSJJUxKEPZ6nFchRV1k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;128&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;target'이란 이름의 부산물이 생겼다. 자동으로 &quot;debug&quot; 내에는&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boCgyg/btsKF5bvqLE/XaxkYbpfL4FaCMqUWPxyY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boCgyg/btsKF5bvqLE/XaxkYbpfL4FaCMqUWPxyY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boCgyg/btsKF5bvqLE/XaxkYbpfL4FaCMqUWPxyY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboCgyg%2FbtsKF5bvqLE%2FXaxkYbpfL4FaCMqUWPxyY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;268&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.exe 실행 파일을 포함해 이런 것들이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복해서 실행해보니, runtime이 0초인 것으로 보아 재컴파일이 필요 없도록 캐싱을 하나보다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Window 환경에서의 개발과 구조화된 프로젝트에 익숙해질 기회가 생긴 것 같다.&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/Rust</category>
      <category>Rust</category>
      <author>mingchin</author>
      <guid isPermaLink="true">https://mingchin.tistory.com/498</guid>
      <comments>https://mingchin.tistory.com/498#entry498comment</comments>
      <pubDate>Tue, 12 Nov 2024 22:25:34 +0900</pubDate>
    </item>
  </channel>
</rss>