8.9 Null 안전성(Null Safety)
8.9 Null 안전성(Null Safety)
Haxe 4.0.0 이후
Haxe 컴파일러는 nullable 값에 대한 옵트인 컴파일 타임 검사를 제공합니다. nullable 값의 다양한 잠재적 문제를 잡으려고 합니다.
Null 안전성 활성화(Enabling Null Safety)
특정 클래스, 필드 또는 표현식에 대해 검사기를 활성화하려면 :nullSafety 메타데이터로 주석 처리하세요. --macro nullSafety("some.package") 초기화 매크로(initialization macro)를 사용해 전체 패키지에 대해 null 안전성을 활성화할 수 있어요.
엄격성 수준(Strictness)
네 가지 null 안전성 엄격성 수준이 있어요:
Off: null 안전성 검사를 끈다. 특정 필드나 표현식에 대해 null 안전성을 선택적으로 비활성화하는 데 유용하다.Loose(기본):if (<expr> != null)조건 안에서<expr>은 검사 후 수정될 수 있더라도 안전한 것으로 간주된다.Strict: 단일 스레드 환경을 위한 전체 규모의 null 안전성 검사.StrictThreaded: 멀티 스레드 환경을 위한 전체 규모의 null 안전성 검사.
기본적으로 null 안전성을 활성화하면 loose 엄격성 수준을 사용합니다. 이는 메타데이터의 인자를 제공해 구성할 수 있어요:
@:nullSafety(Off)
@:nullSafety(Loose)
@:nullSafety(Strict)
@:nullSafety(StrictThreaded)
Strict와 StrictThreaded는 순차 필드 접근의 처리에서 다릅니다. 멀티 스레드 응용 프로그램에서 같은 객체 필드에 대한 순차 접근은 같은 결과를 내지 않을 수 있어요. 즉 필드에 대한 null 검사는 어떤 보장도 제공하지 않습니다:
@:nullSafety(StrictThreaded)
function demo1(o:{field:Null<String>}) {
if (o.field != null) {
// Error: o.field could have been changed to `null`
// by another thread after the check
trace(o.field.length);
}
}
@:nullSafety(Strict)
function demo1(o:{field:Null<String>}) {
if (o.field != null) {
trace(o.field.length); // Ok
}
}
패키지 수준의 경우 null 안전성 엄격성은 선택적 두 번째 인자로 구성할 수 있어요:
--macro nullSafety("some.package", Off)
--macro nullSafety("some.package", Loose)
--macro nullSafety("some.package", Strict)
--macro nullSafety("some.package", StrictThreaded)
상세 사용법(Detailed Usage)
null 안전성은 Null<T>로 명시적으로 선언되지 않은 곳(할당, return 문, 배열 접근 등)에 nullable 값을 전달하지 않도록 합니다.
@:nullSafety
class Main {
static function getNullableStr():Null<String> {
return null;
}
public static function main() {
function fn(s:String) {}
var nullable:Null<String> = getNullableStr();
// all of the following lines would cause a compilation error:
// var str:String = null;
// var str:String = nullable;
// fn(nullable);
}
}
단항·이항 연산자(==와 != 제외)로 nullable을 사용하는 것은 허용되지 않습니다.
필드가 Null<> 없이 선언되면 초기 값이 있거나(인스턴스 필드의 경우) 생성자에서 초기화되어야 합니다.
nullable 타입 매개변수를 가진 매개변수화된 타입의 인스턴스를, 비-nullable 타입 매개변수를 가진 같은 타입이 기대되는 곳에 전달하는 것은 허용되지 않습니다:
var nullables:Array<Null<String>> = ['hello', null, 'world'];
// Array<Null<String>> cannot be assigned to Array<String>:
//var a:Array<String> = nullables;
null에 대해 검사된 지역 변수는 그 null 검사로 덮인 스코프 안에서 안전한 것으로 간주됩니다:
var nullable:Null<String> = getSomeStr();
//var s:String = nullable; // Compilation error
if (nullable != null) {
s = nullable; //OK
}
//s = nullable; // Compilation error
s = (nullable == null ? 'hello' : nullable); // OK
switch (nullable) {
case null:
case _: s = nullable; // OK
}
제어 흐름도 고려됩니다:
function doStuff(a:Null<String>) {
if(a == null) {
return;
}
// From here `a` is safe, because function execution
// will continue only if `a` is not null:
var s:String = a; // OK
}
한계(Limitations)
경계를 벗어난 배열 읽기는 null을 반환하지만, Haxe는 그것을 Null<> 없이 타이핑합니다.
var a:Array<String> = ["hello"];
$type(a[100]); // String
trace(a[100]); // null
var s:String = a[100]; // Safety does not complain here, because `a[100]` is not `Null`, but just `String`
경계를 벗어난 배열 쓰기는 마지막 정의 인덱스와 새로 쓰는 인덱스 사이의 모든 위치를 null 값으로 채웁니다. null 안전성은 이에 대해 보호할 수 없어요.
var a:Array<String> = ["hello"];
a[2] = "world";
trace(a); // ["hello", null, "world"]
var s:String = a[1]; // Cannot check this
trace(s); //null
Haxe는 null 안전성을 염두에 두고 설계되지 않았으므로, 제3자 코드나 심지어 표준 라이브러리에서도 null 값이 코드에 들어올 가능성이 항상 있어요.
Nullable 필드와 속성은 null에 대해 검사한 후에도 null-safe로 간주되지 않습니다. 대신 헬퍼 메서드를 사용할 수 있어요:
using Main.NullTools;
class NullTools {
public static function sure<T>(value:Null<T>):T {
if (value == null) {
throw "null pointer in .sure() call";
}
return @:nullSafety(Off) (value:T);
}
public static function or<T>(value:Null<T>, defaultValue:T):T {
if (value == null) {
return defaultValue;
}
return @:nullSafety(Off) (value:T);
}
}
class Main {
static var nullable:Null<String>;
public static function main() {
var str:String;
if (nullable != null) {
str = nullable; // Compilation error
}
str = nullable.sure();
str = nullable.or('hello');
}
}
지역 변수가 클로저에서 캡처되면 그 클로저 안에서 안전할 수 없습니다:
var a:Null<String> = getSomeStr();
var fn = function () {
if (a != null) {
var s:String = a; // Compilation error
}
}
클로저가 즉시 실행되지 않는 한 말이죠:
var a:Null<String> = getSomeStr();
[1, 2, 3].map(function (i) {
if (a != null) {
return i * a.length; // OK
} else {
return i;
}
});
지역 변수가 클로저에서 캡처되고 nullable 값으로 수정되면, 그 변수는 더 이상 안전할 수 없습니다:
var nullable:Null<String> = getSomeNullableStr();
var str:String;
if (nullable != null) {
str = nullable; // OK
doStuff(function () nullable = getSomeNullableStr());
if (nullable != null) {
str = nullable; // Compilation error
}
}
출처: Null Safety
본문
활성화
:nullSafety 메타데이터로 클래스/필드/표현식에, nullSafety("pkg") 매크로로 패키지에 사용합니다.
엄격성 수준
Off, Loose(기본), Strict, StrictThreaded 네 수준. Strict는 순차 필드 접근의 null 검사 보장을, StrictThreaded는 멀티 스레드에서 접근을 허용하지 않습니다.
규칙
Nullable 값은 Null<T>가 아닌 곳에 전달할 수 없고, 연산자에 쓸 수 없으며(==/!= 제외), 비-null 필드는 초기화가 필요합니다. null 검사된 스코프 안에서만 안전합니다.
한계
경계를 벗어난 배열 접근, 제3자/표준 라이브러리 값, nullable 필드/속성, 클로저 캡처 등은 보호되지 않습니다.