SE-0502, 멤버와이즈 이니셜라이저에서 빠지는 private 프로퍼티
핵심 요약 스위프트 6.4에 반영된 SE-0502는 초깃값을 가진 private 프로퍼티를 구조체의 자동 생성 멤버와이즈 이니셜라이저에서 제외해, 비공개 프로퍼티 하나 때문에 생성자가 잠기던 문제를 없앤다. 바뀐 규칙과 호환 동작을 정리했다.
SE-0502는 스위프트 구조체(struct)에서 컴파일러가 자동으로 만들어 주는 멤버와이즈 이니셜라이저, 즉 각 저장 프로퍼티를 인자로 받는 기본 생성자의 규칙을 손본 제안이다. 초깃값을 가진 private 프로퍼티 하나 때문에 이 생성자 전체가 private로 잠겨 타입 밖에서 못 쓰게 되던 문제를 없앤다. 스위프트 에볼루션 저장소의 SE-0502 문서에 따르면 이 제안은 '일부 수정과 함께 채택'됐고, 문서 상단 상태 표기는 2026년 7월 기준 'Implemented (Swift 6.4)'다. 스위프트 6.4부터는 초깃값이 있는 비공개 프로퍼티가 자동 생성자의 인자 목록에서 조용히 빠진다.
이 글은 struct의 컴파일러 합성 멤버와이즈 이니셜라이저만 다룬다. 직접 손으로 쓴 init이나, 멤버와이즈 이니셜라이저 자체가 없는 클래스에는 해당하지 않는다. 접근 제어를 세밀하게 나눠 쓰는 라이브러리·모듈 작성자가 특히 바로 도움을 받는다.
생성자가 private로 잠기던 문제
스위프트는 struct에 이니셜라이저를 직접 쓰지 않아도 저장 프로퍼티를 순서대로 받는 생성자를 자동으로 만들어 준다. 문제는 이 생성자의 접근 수준이었다. 자동 생성자는 안에 포함된 프로퍼티 중 가장 낮은 접근 수준을 따라갔다. 그래서 계산용 캐시나 내부 상태 같은 private 프로퍼티에 초깃값을 하나 붙이는 순간, 멀쩡히 internal이던 생성자가 private로 내려앉았다.
struct Counter {
var name: String
var limit: Int
private var current = 0
}
// 스위프트 6.4 이전: 아래 호출이 컴파일 에러
let c = Counter(name: "요청", limit: 10)
// initializer is inaccessible due to 'private' protection level
공개 API에는 current가 드러나지도 않는데, 그 프로퍼티 하나 때문에 타입 밖에서는 생성 자체가 막혔다. 해결책은 뻔했다. 필요한 인자만 받는 init을 손으로 다시 쓰는 것이다. 프로퍼티가 두세 개면 참을 만하지만, 필드가 늘거나 프로퍼티 래퍼·매크로가 뒤에서 비공개 저장소를 심는 코드에서는 이 보일러플레이트가 계속 쌓였다.
SE-0502가 바꾼 규칙
핵심은 두 단계다. 스위프트 에볼루션 저장소의 SE-0502 문서에 따르면, 컴파일러는 먼저 멤버와이즈 초기화가 가능한 프로퍼티 중 가장 높은 접근 수준을 계산한다. 단 이 값은 internal을 넘지 않는다. 그다음, 이 수준보다 낮으면서 이미 초깃값을 가진 프로퍼티를 생성자에서 뺀다.
앞의 예에서 name과 limit은 internal이라 최대 수준이 internal로 정해진다. 초깃값을 가진 private 프로퍼티 current는 그보다 낮으니 제외 대상이 된다.
struct Counter {
var name: String
var limit: Int
private var current = 0
}
// 스위프트 6.4: current가 빠져 생성자는 internal로 유지
let c = Counter(name: "요청", limit: 10) // 정상 동작
결과적으로 생성자 시그니처는 init(name:limit:)가 되고 접근 수준도 원래대로 돌아온다. current는 선언에 붙은 0으로 채워진다. 비공개 구현 세부를 숨기려고 생성자를 손수 작성하던 수고가 사라지는 셈이다.
그럼 모든 비공개 프로퍼티가 빠질까
아니다. 초깃값이 있는 프로퍼티만 빠진다. 이게 규칙에서 가장 중요한 조건이다. 제외되려면 두 가지를 동시에 만족해야 한다. 하나는 앞서 계산한 최대 접근 수준보다 낮을 것, 다른 하나는 선언 자체에 초깃값이 있을 것이다.
초깃값 없는 private 프로퍼티는 생성자가 어디선가 값을 받아야 하므로 뺄 수가 없다. 이때는 예전과 똑같이 그 프로퍼티가 인자로 남고, SE-0502가 풀려던 접근 수준 문제도 그대로 남는다. 제안 제목이 그냥 'private properties'가 아니라 'private initialized properties'인 이유가 여기 있다. 빠뜨릴 값을 컴파일러가 이미 알고 있어야만 그 프로퍼티를 안전하게 감출 수 있기 때문이다.
struct Session {
var id: String
private var token: String // 초깃값 없음, 제외 불가
private var retries = 0 // 초깃값 있음, 제외
}
이 struct에서 retries는 생성자에서 빠지지만 token은 남는다. 그래서 token이 남아 있는 한 예전의 제약이 이어진다. 비공개 상태를 밖에서 감추고 싶다면 그 프로퍼티에 합당한 초깃값을 주는 편이 새 규칙과 잘 맞는다.
기존 호출 코드는 그대로 컴파일될까
대체로 그렇다. 제안이 가장 신경 쓴 부분도 이 지점이다. 규칙이 바뀌면 옛 시그니처(init(name:limit:current:))를 직접 부르던 코드가 깨질 수 있다. SE-0502 문서에 따르면 컴파일러는 이를 막기 위해 예전과 동일한 프로퍼티 목록을 가진 호환용 생성자(compatibility overload)를 하나 더 합성한다. 새 시그니처와 옛 시그니처가 함께 존재하는 것이다.
스위프트 언어 운영 그룹(Language Steering Group)은 채택 과정에서 원안을 한 번 손봤다. 옛 생성자를 곧바로 폐기 예정으로 못 박는 대신, 폐기 논의를 향후 과제(Future Directions)로 미루고 두 생성자를 나란히 두는 순수 추가 방식으로 바꿨다. 덕분에 6.4로 올려도 기존 호출부가 갑자기 경고를 쏟아 내지는 않는다.
실무에서 달라지는 것
가장 크게 체감하는 쪽은 모듈 경계를 넘나드는 코드다. 라이브러리에서 공개 struct에 내부 캐시나 상태 플래그를 private로 몰아넣던 사람이라면, 그동안 손으로 유지하던 생성자를 지워도 된다. 프로퍼티 래퍼나 매크로가 뒤에서 private 저장소를 심는 코드도 자동 생성자를 다시 활용할 수 있다. 콜백을 순차 코드로 정리한 async/await처럼, 스위프트는 이런 잔손질을 언어 차원에서 꾸준히 줄여 왔다.
SE-0502 문서 상단 상태는 2026년 7월 기준 'Implemented (Swift 6.4)'로 표기돼 있고, 문서에는 실험적 기능 플래그 ExcludePrivateFromMemberwiseInit가 함께 적혀 있다. 스위프트 버전과 릴리스 소식은 스위프트 공식 사이트에서 확인할 수 있다. 새 버전으로 올리기 전에 자신의 코드베이스가 옛 생성자 시그니처에 얼마나 기대는지 점검해 두면, 호환 생성자 덕에 대부분 무리 없이 넘어가더라도 한결 안심할 수 있다.