30분만에 기본 명령어로 알아보는 Git 버전 관리 기초 실습
무료
입문 / Git
4.7
(3)
개발하려면 누구나 알아야 하는 필수 도구. 사실상 표준 버전관리시스템인 Git을 30분만에 알아보는 기초 강의입니다.
입문
Git
함수형 프로그래밍에 관심이 많은 백엔드 개발자입니다. 카카오(Daum) 개발팀장, NHN 수석, LINE+ 리드를 지내며 개발팀 리딩을 했고, 현재는 디노티시아라는 AI스타트업에서 개발팀장으로 일하고 있습니다.
https://hatemogi.com/ 에 개발자 개인 홈을 운영중입니다.
30분만에 기본 명령어로 알아보는 Git 버전 관리 기초 실습
무료
입문 / Git
4.7
(3)
개발하려면 누구나 알아야 하는 필수 도구. 사실상 표준 버전관리시스템인 Git을 30분만에 알아보는 기초 강의입니다.
입문
Git
자바 11 LTS 업데이트 - JAVA 9, 10, 11 새기능 요약
₩16,500
초급 / Java
5.0
(3)
자바를 쓰고 있지만, 아직 자바 8 문법만 활용하고 계신가요? 자바 11 LTS에 새로 추가된 유용한 기능들을 알아보고 더 편리하게 자바를 활용하는 유능한 개발자가 되어보세요. 여기서 소개하는 기능들은 11 LTS, 17 LTS, 21 LTS버전 모두에서 유용하게 활용하실 수 있습니다.
초급
Java
우아한 고성능 프로그래밍 언어 Rust 입문 및 활용
₩55,000
초급 / Rust, webassembly
4.7
(65)
누구나 탄탄하고 효율적인 고성능 소프트웨어를 만들 수 있게 해주는 프로그래밍 언어, Rust를 배우는 강의입니다. 로우레벨 프로그래밍을 할 수 있으면서도 하이레벨 언어기능이 풍부하여 이상적입니다.
초급
Rust, webassembly
순수 함수형 프론트엔드 언어 Elm 가이드
무료
초급 / elm, 함수형 프로그래밍, SPA, 웹앱
5.0
(5)
순수 함수형 프로그래밍으로 프론트엔드 웹앱을 만드는 Elm 프로그래밍 언어 가이드 문서를 3시간만에 다 공부합니다.
초급
elm, 함수형 프로그래밍, SPA
예제로 뿌수는 코틀린 Kotlin 76제
₩44,000
초급 / Kotlin, Android, backend
5.0
(15)
76개의 예제로 실습하며 공부하는 코틀린 Kotlin 프로그래밍 언어. 당장 내일부터 코틀린으로 개발해야 하는데, 차근히 문법을 공부할 여유가 없나요? 이 강의에서 알려드리는 예제로 빠르게 코틀린의 모든 문법을 알아봅시다.
초급
Kotlin, Android, backend
바쁜 자바 개발자를 위한 스칼라 맛보기
₩55,000
초급 / 함수형 프로그래밍, scala, scala-3, Java, 객체지향
4.8
(16)
스칼라를 사용하면 자바 개발자로 쌓아온 경험을 그대로 살리면서, 훨씬 간결하고 탄탄한 코드를 빠르게 작성할 수 있습니다. 이 강의는 자바 개발자를 대상으로 기존 자바 지식을 발판 삼아, 빠르게 스칼라의 전체적인 모습을 이해할 수 있게끔 구성했습니다. 기존 자바 개발자라면 아주 쉽게 스칼라도 배우실 수 있습니다.
초급
함수형 프로그래밍, scala, scala-3
질문&답변
소유권 이전과 관련된 질문입니다
안녕하세요. AI인턴이 너무 훌륭하게 답을 해주어서, 제가 더 드릴 말씀이 없네요. 혹시 부연이 필요하신 부분이 있으시거든 추가질문 편하게 주세요.Rust는 컴파일 타임에 깐깐하게 더 검사해주니, 더 믿고 편하게 쓸 수 있다 정도로 보시면 좋을 것 같습니다.
질문&답변
&r1 을 써야하는지 r1을 써야하는지?
fn main() { let s = String::from("헬로"); let r1: &String = &s; let r2: &&String = &r1; let r3: &&&String = &&&s; println!("r1 = {}, r2= {}, r3 = {}", r1, r2, r3); }수강 및 좋은 질문 감사합니다.우선, println!("{}", v)에서 어떤 값이 출력됐다고 해서, 그 값이 그대로 보인 게 아니라는 점을 명확히 하면 좋겠습니다.질문에 적어주신대로, r1은 s의 주소값, 러스트 용어로 말하자면 참조값인 게 맞습니다. 둘의 타입이 명백히 다르죠. s의 타입은 String이고, r1의 타입은 &String입니다.단지, println!매크로에 활용되는 std::fmt::Display 트레이트 구현과 러스트의 deref 규칙 때문에 화면에는 "헬로"라는 문자열이 잘 보인 것 뿐입니다.강의에 따로 다룬 내용이 아니므로 자세히 설명드리기는 어렵습니다만, 간단하게 적자면, println!에 {}자리에 찍을 대상들은 Display::fmt 메소드를 호출하게 되어있고, 참조값인 경우 원형(참조를 해제해서)의 fmt까지 쫓아가서 출력할 수 있는 기능이 있습니다.따라서 몇 중으로 참조한 참조값이든 원래 타입의 값이 Display할 수 있는 값이라면 같은 내용이 화면에 보이는 것입니다.질문에 적어주신, r1을 써야하느냐, &r1을 써야 하느냐는 질문에는, 상황에 따라 다를 수 있지만, 일반적으로 이미 r1이 참조값이므로, 그걸 또 참조할 일은 흔치는 않겠다고 말씀드리겠습니다. (하지만 C/C++같은 언어에서 때로 이중포인터를 쓰기도 하니, 비슷한 용례로 쓰일 수도 있겠죠)더 궁금하신 내용이 있으시면 편히 말씀주세요
질문&답변
임대값 수명에 대한 질문
이번에도 좋은 질문 감사합니다.제가 강의에서 말한 내용과 링크주신 문서의 내용이 사실 같은 내용입니다.저도 처음에 헷갈린 부분인데요, 임대값의 수명을 관리하는 것이 실제로 원래값의 생명에 영향을 주지 않습니다. 내가 임대한 것을 더 길게 쓰고 싶다고 해서 원래값이 메모리에 더 남아 있거나 하는 게 아닌 거죠. 단지 원래 값들은 소유자가 선언된 스코프에서 벗어나면 그저 메모리에서 사라질 뿐입니다. 임대값들은 그 원래값이 유지된 범위 안에서만 활용할 수 있기에, 임대검사기가 그 임대값들이 해당 수명 범위 안에서 쓰이고 있는지를 컴파일 시간에 검사해주는 것입니다.채택이라는 표현으로 보아, 아마도 어떤 수명으로 확보되는 것으로 이해하신 게 아닌가 추측되는데요, 채택이 아니라, 그 수명이 확보되는 조건이어야만 컴파일이 된다라고 이해하면 어떨까 싶습니다. s1과 s2는 원래값의 참조값인데, 원래 값들의 수명이 있을 것이고, 그 수명보다 반환값의 수명이 같거나 짧아야 문제가 없겠죠. 제가 발언한 내용은 원래값들의 수명이 더 길어야 한다고 얘기한 것이라서, 어느 측면에서 보느냐에 따라 뭐가 긴지 짧은지가 다른 것뿐, 사실 같은 내용입니다. a >= b 이어야 하는데, a입장에서는 같거나 커야한다는 의미가 되는 것이고, b입장에서는 같거나 작아야 한다는 뜻인 거죠. 핵심은, 참조값의 수명은 원래값의 수명보다 같거나 짧아야만 한다.그걸 문제없는지 확인해주는 것이 임대검사기다라고 이해해주시면 좋을 것 같습니다. 다시 정리하면, longest함수에서 쓰인 참조값 s1, s2, 반환값(이 경우 반환값도 참조값) 3개 참조값의 수명을 놓고 볼 때, 반환값의 수명은 s1이나 s2의 수명과 연결되는데, 뭐일지는 실제 런타임시의 값에 따라 다르니까 둘 중 짧은 수명을 기준으로 반환값의 수명이 만족되어야 하는 상황입니다. 이게 링크주신 문서에 있는 내용이고, 제가 강의에서 말한 내용은, 반환값의 수명보다 s1, s2의 수명이 같거나 길어야한다는 내용이므로, 이는 같은 뜻이에요 s1의 수명과 s2의 수명 중 짧은 것의 수명이 반환값의 수명이 된다==반환값의 수명보다, s1의 수명과 s2의 수명이 같거나 길어야 한다. 명확해졌기를 바랍니다.
질문&답변
변수 가리기 shadowing 을 듣고 궁금한점이 생겼습니다.
안녕하세요 예인님, 수강 및 좋은 질문 감사합니다. 해당 내용은, 소유권 이동과 빌림을 듣고 나시면 더 상세히 이해하실 수 있는 내용이라, 해당 주제까지 학습하신 다음에 말씀주시면 그 때 더 상세히 설명드릴게요. 미리 설명을 힌트를 조금 드리자면, 변수에 자리잡은 메모리는 러스트가 소유권이라는 개념으로 딱 하나의 변수만 그 메모리를 소유하여 관리를 하고, 그 소유한 변수가 스코프를 벗어나면 해당 메모리가 해제되는 시스템입니다. 이 경우 '변수 가리기'로 어떤 메모리의 값이 다른 새로운 변수로 가려지게 되면, 가림 당한 변수는 이미 소유권을 빼앗긴 꼴이라 더 소유하지 못하고 사용도 못하게 될 터입니다. { // 바깥 블록 let x = 1; { // 안쪽 블록 let x = x + 1; } // 안쪽 블록을 나온 다음에 x 접근 가능한가? println!("x = {}", x); }학습하신 예제에서 소유권 개념이 아직 나오지 않았는데요, i32같은 타입들은 소유권 이동이 일어나지 않고 Copy가 일어나기 때문에, 가려진 변수나 새로운 변수나 모두 각자 메모리 값을 소유한 상태와 마찬가지이고, 각각 스코프를 벗어날 때 해제되는 상태예요. Copy트레이트로 소유권 복사가 일어나지 않는 변수의 경우에는, 이미 안쪽블록으로 소유권이 넘어갔을 테고, 바깥에서 원래 변수를 쓰려고 하면 이미 소유권이 없기 때문에 더 쓰지 못하는 상황이 될 거예요. 둘 다 쓰고 싶다면, shadowing할 때 clone()을 해서 가져가면 되겠습니다. 소유권 강의도 듣고 나서 다시 말씀 부탁드려요.
질문&답변
메모리 관리규칙에서 질문드립니다.
수강 및 예리한 질문 감사드립니다.실험해보신 내용처럼 (s, s.len())으로 응답 튜플의 순서를 바꾸었을 때, 아래와 같은 컴파일 오류가 발생하는 것을 확인하였습니다. (사진) 에러의 내용은, 튜플의 첫번째 요소로써, s를 반환하기로 했기 때문에, 이 시점에 이미 소유권 반납이 이뤄졌다고 보는 거라고 이해하면 될 것 같습니다. 이미 소유권이 반납된 s를 이용해서 .len()메소드를 호출하려 할 때의 상황입니다. 이 문제를 해결하려면, len을 미리 값을 구해두면 되겠습니다. fn main() { let s: String = String::from("헬로"); let (s, len) = string_length(s); println!("문자열 {}의 길이는 {}", s, len); } fn string_length(s: String) -> (String, usize) { let len = s.len(); (s, len) }괜한 번거로움이니, 원래대로 usize를 앞에 담은 튜플을 반환하는 것이 편하겠고, 더 나아가 이후 강의에 설명드리는 임대 개념을 사용하면 좋을 것 같습니다.우선 이 질문과 답변의 핵심은, 소유권의 이동 시점에 관한 것이겠습니다. 감사합니다.
질문&답변
튜플과 구조체 차이 설명에 관한 질문
수강 및 좋은 질문 감사합니다. 구조체와 튜플 혼동 차이struct Color(i32, i32, i32); struct Point(i32, i32, i32);사실 Color는 보통 (u8, u8, u8) 같은 타입을 쓰겠지만, 편한 비교를 위해 i32 3개로 구성했다고 칩시다. 이 상황에서fn drawSphere(point: Point, color: Color) { // ... 반지름 1인 구를 그리는 함수 } fn main() { let point = Point(0, 0, 0); let color = Color(255, 255, 255); drawSphere(point, color); }이렇게 원점에다가 흰색의 반지름 1짜리 구를 그리려는 상황이라면, drawSphere에 첫번째 파라미터에는 반드시 Point타입의 구조체 튜플이 들어가야 하고, 두번째는 반드시 Color타입의 구조체 튜플이 들어가야 하므로, 두 파라미터가 섞일 일이 없겠습니다.fn drawSphere(point: (i32, i32, i32), color: (i32, i32, i32)) { // ... 반지름 1인 구를 그리는 함수 } fn main() { let point = (0, 0, 0); let color = (255, 255, 255); drawSphere(color, point); }그런데 이번에는 그냥 평범한 튜플을 써보았는데요, 이때는 두 값의 타입이 (i32, i32, i32)로 동일하므로, drawSphere를 호출하는 시점에, 두 파라미터를 혼동해서 거꾸로 적었음에도, 타입체커 입장에서는 문제가 없으므로 정상 컴파일이 되겠습니다. 하지만, 실행하면, (255, 255, 255) 좌표에 검은색 구가 그려지겠죠.이런 상황을 말씀드리는 것이긴 합니다. 하지만 한편, 요새는 코드 에디터에서 명시적으로 파라미터 이름을 잘 보여주기도 하니, 크게 문제가 되지 않을 수도 있기도 하겠습니다.구조체 튜플과 구조체 끝에 세미콜론 차이사실 저도 크게 모르고 쓰고 있는 부분인데, 이 부분은 러스트 컴파일러나 린터, 포맷터가 알아서 잘 관리해주기 때문에 시키는대로 하면 되는 부분 같습니다. 아마도 문법적으로나 관습적으로 두 경우에 세미콜론 사용여부를 갈라놓은 것 같습니다. 정확한 것은 문법 규칙을 자세히 살펴보면 알 수 있겠습니다.(사진)그냥 편하게는 코드에디터에서 (아마도 러스트 포맷터나 린터가 알려주는 정보를 토대로) 'braced struct declarations are not followed by a semicolon'이라면서 잘 알려주고 빨갛게 표시해주므로, 세미콜론 없이 쓰면 되는 상황입니다. 혹시 더 궁금하셔서 문법 규칙을 살펴보셨다면, 제게도 알려주시면 감사하겠습니다. 감사합니다.
질문&답변
trait를 인자로 받을 때 &impl과 &dyn차이
심도 깊은 질문 감사합니다. impl은 정적 디스패치라고, 컴파일 타임에 해당 트레이트를 구현한 구체적인 타입을 파악할 수 있는 경우이고, dyn같은 경우 동적으로 런타임에 파악하는 경우에 활용합니다. 전자는 컴파일타임에 구체적인 정보를 파악해야 하는 대신에 성능 페널티가 없고, 후자는 컴파일 타임에 미리 정해지지 않은 해당 트레이트 구현체를 활용할 수 있지만, 런타임에 해당 구현체를 쫓아가야 하는 부담이 있습니다.해당 주제는 중급 강의에 잘 정리해볼게요.수강 및 질문 감사드립니다.
질문&답변
구조체는 언제나 Heap에 저장되나요??
예리한 질문 감사드립니다. 덕분에 저도 다시 한번 학습하게 되었습니다. 구현하신 구조체는 스택에 저장되는 것이 맞겠습니다. 구성멤버의 메모리 크기가 컴파일타임에 사전 결정되기 때문에 더욱 가능합니다. 스택과 힙 메모리 어디에 할당되는가를 중요하게 설명드렸기에 혼란을 끼친 것 같습니다.소유권 이전은 사실 엄밀하게 따지면 소유권의 대상(즉 메모리를 차지하고 있는 데이터)이 복사 되는가 아닌가가 더 중요하다고 볼 수 있습니다. 스택에 위치하는 기본 데이터형(i32 등)은 기본적으로 Copy 트레이트가 구현된 상태이고, 직접 선언하는 Struct는 기본적으로는 Copy트레이트가 구현되지 않습니다. 따라서 복제(Copy)가 발생하지 않고 소유권이 이동되어 버리는 상황인데요, 구현하신 코드에서 해당 구조체에 대해 Copy트레이트를 구현하신다면, 소유권이 이동되지 않고 복사되어 활용 가능합니다. #[derive(Clone, Copy)] struct Rectangle { width: u32, height: u32, } fn main() { let rect = Rectangle { width: 20, height: 30, }; println!( "가로가 {}, 세로가 {}인 사각형의 면적은 {}이다.", rect.width, rect.height, area(rect) ); print!("{:?}", rect.width); } fn area(rect: Rectangle) -> u32 { rect.width * rect.height } 이렇게 구현하시면 컴파일됩니다. 첫줄의 derive를 이용해서 Copy트레이트를 구현하였기 때문입니다. 대신 main함수의 스택공간과, area함수의 스택 공간 모두를 중복해서 차지하고 있게 되겠습니다.다시 정리하면, Heap을 차지하는 데이터라고 하더라도 Copy트레이트를 구현하면 내용이 복사되면서, 소유권 이동이 일어나지 않겠습니다.중급과정을 제작할 예정인데, 보충해서 잘 설명해보도록 하겠습니다. 수강과 질문 감사드립니다.
질문&답변
혹시 어떤 폰트일까요?
여러 폰트를 썼는데요, 고운돋움IBM Plex Sans KRPretendard 입니다. 도움이 되셨기를 바랍니다.
질문&답변
Lifecycle 강의에서 두 문자열 슬라이스를 비교해 긴 문자열 슬라이스를 리턴하는 것 관련 질문
예리한 질문 감사드립니다. 네, 아무래도 소유권과 임대, 그리고 생명주기(라이프사이클)등이 좀 생소하기 때문에 설명과 이해가 어려운 것 같습니다. 아마 아래 예제에 대한 질문이신 것 같습니다. fn str_lifetime() { let s1 = String::from("가나다"); let s2 = "하나둘셋"; let res = longest(s1.as_str(), s2); println!("더 긴 문자열은 {}", res); }/* 수명 표기를 잘 한 경우 */ fn longest(s1: &'a str, s2: &'a str) -> &'a str { if s1.len() > s2.len() { s1 } else { s2 } }러스트의 함수에서 로컬 변수값을 리턴하는 것은 문제가 되지 않습니다. 로컬 변수에 대한 소유권이 함수 안에 있다했을 때, 반환값으로 넘어가면 해당 소유권이 함수 호출자에게로 이전되고, 호출한 입장에서는 해당 반환값을 문제없이 잘 활용할 수 있습니다.게다가 소유권 "임대"의 경우에는 애초에 나한테 있던 소유권이 아니라 빌려왔던 것이기 때문에, 외부의 참조값을 빌려온 함수 입장에서는 애초에 내 소유가 아닙니다. 반환을 하든 안하든 애초에 내 소유가 아니었기 때문에 소유권 고민 문제에서 자유롭게 됩니다. 해당 라이프사이클에 대한 설명은, 소유권 임대의 상황에서, 그 임대값 참조의 수명을 명시해야 할 필요가 있는 경우에 대한 것인데요, 강의 중에 설명을 한 내용입니다만, 여기 글로 다시 정리해보겠습니다.longest함수에서 s1과 s2라는 문자열슬라이스, 그러니까 참조값을 임대해 와서 무언가를 하고 있습니다. 코드 안에서는 s1과 s2의 len()함수를 써서 길이를 판단해보고, 더 긴 참조값을 반환했습니다. 러스트 컴파일러 입장에서는 런타임에서 달라질 수 있는 (사실 이 코드에서는 s1과 s2의 구체적인 값이 하드코딩되어있기 때문에 어쩌면 알 수도 있겠습니다만, 일반적으로 문자열 값은 런타임에 다른 값이 발생하겠죠) 문자열들의 길이를 미리 알 수가 없습니다. 그러니 s1참조값과 s2참조값 중 어느 것이 반환될지 모르는 상황입니다.이럴 때, 러스트 프로그래머가 둘 중 어느것이든 반환 될 수 있고, 그 라이프사이클이 반환값의 라이프사이클 조건과 맞을 수 있도록 명시해주는 작업인 거죠. 그렇게 컴파일러의 임대 검사기 (borrow checker)가 'a라는 동일 lifecycle 기준으로 검사를 진행할 수 있게 해주는 상황입니다.다시 글로 적어도 어려운 내용인 것 같습니다. 추가 설명드리면 좋을 부분 있으시면 편히 말씀 부탁드립니다. 수강해주시고, 질문 남겨주셔서 감사합니다.
김대현님의 소개 - 인프런