새 솔버의 불투명 타입
새 솔버에서 불투명 타입을 처리하는 방식은 기존 구현과 다릅니다. 이 문서는 새 솔버의 동작을 다른 자료 없이도 이해할 수 있도록 설명합니다.
비정의 사용과 정의 사용
비정의 사용과 정의 사용의 구분은 추론 동작을 결정하고 추상화의 엄격성을 좌우합니다. 정의 사용은 그 밑에 숨겨진 타입을 드러냅니다. 반면 비정의 사용은 타입을 추상적인 자리 표시자로 취급하여 경직성을 강제합니다.
비정의 사용
이 개념이 불투명 타입을 경직되게 만듭니다. 솔버는 그 밑의 타입을 알 수 없는 것으로 취급하여 추상화를 유지합니다.
#![allow(unused)]
fn main() {
fn foo() -> impl Copy {
let x: u32 = 56;
x
}
fn bar() {
let x = foo();
// Even though we know 'foo' returns a u32.The solver enforces
// rigidity here and is a NON-DEFINING use.
}
}
정의 사용
불투명 타입이 실제로 정의되는 곳이 바로 여기입니다. 정의 사용은 추상화 뒤에 있는 구체 타입이 정확히 무엇인지 컴파일러에 알려 줍니다.
#![allow(unused)]
fn main() {
fn foo() -> impl Copy {
let x: u32 = 56;
// The return value x defines the hidden type as u32 and is a DEFINING use.
x
}
}
불투명 타입은 별칭 타입입니다
불투명 타입은 가능한 한 다른 별칭, 특히 연관 타입과 똑같이 취급됩니다. 동작상의 차이는 가능한 한 적어야 합니다.
불투명 타입은 숨겨진 타입으로 정규화될 수 있습니다. 또한 완전성에 대한 요구 사항도 같습니다. 이런 점에서 다른 별칭 타입과 매우 비슷하므로, 이렇게 취급하는 것이 바람직합니다. 이렇게 취급하면 코드를 공유하게 되므로 타입 시스템의 복잡성도 줄어듭니다. 불투명 타입을 따로 다뤄야 한다면 규칙이 더 복잡해지고 새로운 종류의 상호작용이 생깁니다. 암시적 부정 모드에서는 불투명 타입을 다른 별칭처럼 취급해야 합니다. 따라서 모드마다 동작이 크게 다르면 그만큼 복잡성이 늘어납니다.
열린 질문: 여기서 다른 접근 방식이 있을 수 있습니까? 예를 들어 불투명 타입을 경직된 타입에 더 가깝게 취급하고, 인스턴스화할 수 있는 위치를 더 제한하는 방식은 어떻습니까? 그렇게 하더라도 일관성 검사 중에는 여전히 일반적인 별칭이어야 합니다
불투명 타입에 대한 normalizes-to
새 솔버는 normalizes-to를 사용하여 별칭의 한 단계 정규화 동작을 정의합니다. 예를 들어 <<T as IdInner>::Assoc as IdOuter>::Assoc는 먼저 <T as IdInner>::Assoc로 정규화되고, 이것이 다시 T로 정규화됩니다. 이것은 정규화 대상인 AliasTy와 기대되는 Term을 모두 받습니다. 실제 정규화에 normalizes-to를 사용하려면 기대되는 항으로 제약 없는 추론 변수를 넘기기만 하면 됩니다.
정의 스코프 안의 불투명 타입과 암묵적 부정(implicit-negative) 일관성 모드에서는 이 작업이 항상 두 단계로 수행됩니다. 정의 스코프 밖에서는 불투명 타입에 대한 normalizes-to가 항상 Err(NoSolution)을 반환합니다.
먼저 기대되는 타입을 숨겨진 타입으로 할당해 봅니다.
현재 암시적 부정 일관성 모드에서는 이 단계가 불투명 타입 저장소와 상호작용하지 않고 항상 모호성으로 끝납니다. 대신 모든 불투명 타입을 ’정의’할 수 있게 허용하고 마지막에 추론된 타입을 버리는 방법도 있습니다. 다만 이렇게 하면 일관성 검사 중에 불투명 타입이 여러 번 사용될 때의 동작이 달라집니다: 예시
정의 스코프 안에서는 먼저 불투명 타입의 타입 인자와 상수 인자가 모두 플레이스홀더인지 확인합니다: 소스. 이 검사가 모호하면 모호성을 반환하고, 실패하면 Err(NoSolution)을 반환합니다. 이 검사는 borrowck의 마지막 단계에서만 검사되는 리전(region)을 무시합니다. 검사가 성공하면 계속 진행합니다.
그다음 불투명 타입의 제네릭 인자를 불투명 타입 저장소에 이미 있는 불투명 타입의 인자와 의미적으로 단일화할 수 있는지 확인합니다. 단일화할 수 있다면, 이전에 저장된 타입을 이 normalizes-to 호출의 기대 타입과 단일화합니다: 소스1.
그렇지 않으면 기대 타입을 불투명 타입 저장소에 삽입합니다: 소스2. 마지막으로, opaque 타입의 항목 바운드가 기대 타입에 대해 성립하는지 확인합니다: 소스.
정규화 가능한 별칭의 별칭 바운드(alias-bounds) 사용
https://github.com/rust-lang/trait-system-refactor-initiative/issues/77
연관 타입은 ParamEnv 후보를 통해 정규화했을 때의 결과 타입보다 더 강한 바운드를 가질 수 있으므로, 정규화 가능한 별칭에 AliasBound 후보를 사용하는 것은 일반적으로 불가능합니다.
이러한 후보를 사용하면 정확한 정규화 전략이 사용자에게 드러나게 됩니다. 그렇지 않은 경우에는 즉시 정규화하는지 여부를 사실상 관찰할 수 없습니다. 어디에서 정규화할지는 기존 솔버 지원을 제거한 뒤에 바꾸고 싶어질 가능성이 높으므로, 그렇게 되는 것은 바람직하지 않습니다.
불투명 타입은 어디에서나 정의될 수 있습니다
정의 스코프 안의 불투명 타입은 단순히 타입을 관계 지을 때든 트레이트 솔버 안에서든 어디에서나 정의될 수 있습니다. 이로써 순서 의존성과 불완전성이 제거됩니다. 이것이 없으면 미묘한 이유로 목표의 결과가 달라질 수 있습니다. 예를 들어 불투명 타입의 첫 번째 정의 사용 전에 그 불투명 타입을 사용하는 목표를 평가하려 하는지에 따라 결과가 달라질 수 있습니다.
정의 스코프 안의 고차 불투명 타입
이는 지원되지 않으며, 현재 이를 정의하려고 하면 항상 오류가 발생해야 합니다.
FIXME: 이제 불투명 타입 저장소에서 불투명 타입을 조회할 때 영역이 단일화될 수 있으므로, 불투명 타입이 플레이스홀더를 참조하지 않는지 미리 확인해야 합니다. 그렇지 않으면 결국 플레이스홀더가 누출됩니다.
멤버 제약
멤버 제약의 처리 방식은 새 솔버에서도 바뀌지 않습니다. 이에 대해서는 기존 관련 장을 참고하십시오.
불투명 타입에 대한 메서드 호출
FIXME: 정의 스코프 안에서 아직 제약되지 않은 불투명 타입에 대한 메서드 호출을 계속 지원해야 합니다. 이를 가장 잘 수행하는 방법은 아직 명확하지 않습니다.
#![allow(unused)]
fn main() {
use std::future::Future;
use futures::FutureExt;
fn go(i: usize) -> impl Future<Output = ()> + Send + 'static {
async move {
if i != 0 {
// This returns `impl Future<Output = ()>` in its defining scope,
// we don't know the concrete type of that opaque at this point.
// Currently treats the opaque as a known type and succeeds, but
// from the perspective of "easiest to soundly implement", it would
// be good for this to be ambiguous.
go(i - 1).boxed().await;
}
}
}
}