콘텐츠로 이동

선언과 타입

파일 구성 순서
[package 선언] // 0개 또는 1개
[import 선언...] // 0개 이상
[최상위 선언...] // class, interface, enum, schema

순서를 바꾸면 거절됩니다.

잘못된 배치 진단
package를 두 번 선언 Package declaration must appear only once.
import 뒤에 package Import declarations must appear after the package declaration.
선언 뒤에 package Package declaration must appear before top-level declarations.
선언 뒤에 import Import declarations must appear before top-level declarations.

최상위에는 타입 선언만 둡니다. 클래스 밖의 함수·변수는 Unsupported top-level declaration 오류입니다. 한 파일에 여러 클래스·인터페이스를 둘 수 있습니다.

package app.orders;

점으로 구분한 이름 공간입니다. 패키지 이름이 폴더 구조를 자동으로 만들지는 않습니다.

import unibase.collections.List; // 타입 하나
import unibase.concurrent.*; // 패키지의 모든 타입
  • use, using, from … import 형태는 없습니다. 같은 import를 두 번 쓰면 Duplicate import 오류입니다.
  • 두 import가 같은 단순 이름을 가져오면 Ambiguous type reference 'X'. Use a qualified name. 오류입니다. 이때는 unibase.db.mysql.Database처럼 전체 이름을 씁니다.
  • 이름은 현재 파일·패키지의 타입을 먼저 찾고 import를 찾습니다.

다음 패키지는 import 없이 자동으로 읽습니다.

패키지 대표 타입
unibase.lang Exception, ArgumentException, StateException, IndexException, IOException, TimeoutException, CancelledException, DateTime, JsonValue
unibase.collections List<T>, Set<T>, Map<K, V>, MapEntry<K, V>
unibase.system Console
unibase.io File, FilePath
unibase.concurrent Async, Future<T>, Completer<T>, Cancellation
unibase.net Socket, ServerSocket, GrpcSchema
unibase.db Database, SqlText, SqlStatement<T>, ResultSet, 실행 결과 타입
unibase.core.ui, unibase.core.ui.window UI 위젯·Window

자동으로 읽는 패키지가 있어도 학습·공유용 코드에서는 사용하는 타입을 명시적으로 import하면 읽기 쉽습니다. unibase.db.mysql 같은 하위 패키지는 자동으로 읽지 않습니다. 타입 목록은 Standard 라이브러리를 참고하세요.

class App {
static void main() {
print("start");
}
}
  • 실행 프로그램(unic check, unic run, unic build)에는 어느 클래스에든 static void main()이 있어야 합니다. 없으면 unic requires static void main(). 오류입니다.
  • main 본문에는 실행 문장이 하나 이상 있어야 합니다. 빈 main은 unic requires at least one executable statement inside main. 오류입니다.
  • public은 생략할 수 있습니다. 명령행 인수 배열 string[] args는 지원하지 않습니다(배열 타입이 없습니다).
  • 시작점 없이 클래스만 모은 라이브러리 소스는 unic check <파일> --library로 검사합니다.
클래스 선언 형태
[수식어...] class 이름[<타입매개변수, ...>] [extends 부모클래스] [implements 인터페이스, ...] {
필드, 생성자, 메서드
}
전체 소스: Shapes.uni
public interface Shape {
double area();
string label();
}
public class Rect implements Shape {
protected double width;
protected double height;
public Rect(double width, double height) {
this.width = width;
this.height = height;
}
public double area() {
return width * height;
}
public string label() {
return "rect";
}
}
public class Square extends Rect {
public Square(double side) {
this.width = side;
this.height = side;
}
public string label() {
return "square";
}
}
class Shapes {
static void main() {
Shape shape = Square(3.0);
print(shape.label() + " " + shape.area());
}
}
  • extends는 하나만, implements는 쉼표로 여러 개 쓸 수 있습니다. 순서는 extends → implements입니다. extends A, B는 Invalid base class name 오류입니다.
  • 클래스 본문에 둘 수 있는 것은 필드·생성자·메서드뿐입니다. 중첩 클래스, 초기화 블록, 속성(get/set) 선언은 Unsupported class member declaration 오류입니다.
  • 모든 클래스가 자동으로 상속하는 공통 부모 타입은 없습니다. object, any 타입도 없습니다.
규칙 위반 시 진단
final class는 상속할 수 없음 Class 'B' cannot extend final class 'A'.
클래스는 인터페이스를 extends할 수 없음 Class 'X' cannot extend interface 'Y'.
클래스를 implements할 수 없음 Class 'X' cannot implement class 'Y'.
인터페이스의 모든 메서드를 구현 Class 'P' must implement method 'name()' from interface 'Named'.
구현 메서드의 반환 타입은 인터페이스 반환 타입에 대입 가능 Method 'm(int)' in class 'X' must return T to implement interface 'I'.
상속 순환 금지 Cyclic class inheritance involving 'A'.
  • 부모 클래스와 같은 이름·매개변수의 메서드를 자식에 선언하면 자식의 메서드가 사용됩니다. override 키워드는 없고 붙이지 않습니다.
  • abstract 클래스·메서드는 없습니다. 구현을 강제하려면 인터페이스를 사용합니다.
  • 인터페이스 구현 메서드는 static이 아니어야 하고 매개변수 타입이 정확히 같아야 합니다.
  • 부모 생성자를 직접 호출하는 super(...) 문법은 없습니다. 자식 생성자는 상속받은 상태를 위 Square처럼 this.필드 = 값으로 준비합니다. 생성 코드의 부모 초기화 방식은 대상에 맞춰 구성됩니다.
  • 부모의 메서드는 super.메서드()로 호출할 수 있습니다.
  • 상속받은 필드는 this.shared처럼 this.를 붙여 사용합니다. 현재 클래스에 선언한 필드만 이름만으로 참조할 수 있습니다.
인터페이스 선언 형태
[수식어...] interface 이름[<타입매개변수, ...>] [extends 인터페이스, ...] {
반환타입 메서드이름(매개변수...);
}
  • 메서드 선언만 둘 수 있고 본문(기본 구현)은 둘 수 없습니다. 필드·본문이 있으면 Unsupported interface member declaration. 오류입니다.
  • 인터페이스 메서드의 기본 접근은 public입니다.
  • 인터페이스는 여러 인터페이스를 extends할 수 있지만 클래스를 상속할 수 없습니다.
전체 소스: OrderStatus.uni
public enum OrderStatus {
CREATED = "created",
PAID = "paid",
CANCELLED = "cancelled",
}
class Orders {
static void main() {
OrderStatus status = OrderStatus.PAID;
print(status == OrderStatus.PAID);
}
}
  • 모든 항목에 문자열 값을 지정합니다. enum Status { OPEN }은 Enum requires an explicit string value., 숫자 값은 Enum value must be a string literal. 오류입니다.
  • 항목 사이에는 쉼표를 쓰고 마지막 쉼표는 생략할 수 있습니다. 항목은 1개 이상 1024개 이하입니다.
  • 수식어는 public만 사용합니다. 어노테이션은 추적 관계 @Realizes·@References, 설명 @Description·@Notes·@Title과 설계 메모 어노테이션(@Stereotype, @Status, @Policy, @Relation, @Step, @Param)만 붙일 수 있습니다. 이름·값이 중복되면 Duplicate enum member name., Duplicate enum wire value. 오류입니다.
  • enum에는 필드·메서드·생성자·타입 매개변수를 둘 수 없습니다. 각 항목은 OrderStatus.PAID처럼 사용하는 enum 타입의 상수입니다. enum은 상속·생성(OrderStatus())할 수 없습니다.
  • 제한 지원: 현재 native 실행 대상은 enum을 지원하지 않습니다(Native target has no string enum representation). 서버·화면 코드 생성 프로젝트에서 사용하는 선언입니다.
수식어 최상위 class interface 필드 메서드·생성자 의미
public ○ ○ ○ ○ 어디서나 접근
protected ○ ○ ○ ○ 같은 패키지와 자식 클래스에서 접근
private ○ ○ ○ ○ 선언한 클래스 안에서만 접근
static ○ ○ 객체가 아닌 클래스에 속함
final ○ ○ 클래스: 상속 금지 / 필드: 다시 대입 금지
const ○ ○ 필드: final과 같은 읽기 전용 / 생성자: 본문 없는 const 생성자
native ○ ○ ○ 본문을 런타임이 제공 — 문법 확장
partial ○ 여러 파일로 나눈 클래스 조각 — 문법 확장
  • 같은 수식어를 두 번 쓰면 Duplicate modifier, 접근 수식어를 두 개 쓰면 Conflicting visibility modifiers 오류입니다. 필드에 final과 const를 함께 쓰면 Duplicate readonly field modifier 오류입니다.
  • abstract, override, sealed, async, synchronized는 없습니다. 메서드 앞에 쓰면 Invalid method return type. 오류입니다.
  • 최상위 타입의 static, const는 지원하지 않습니다.
선언 위치 수식어가 없을 때
최상위 class·interface·enum 같은 패키지에서 사용
클래스의 필드·생성자·메서드 private
인터페이스의 메서드 public

생성자에 public을 쓰지 않으면 private이 되어 다른 클래스에서 호출할 수 없습니다 (No matching constructor for P at variable 'p'.). 다른 클래스에서 만들 객체의 생성자와 사용할 메서드에는 public을 붙이세요. 생성자를 하나도 선언하지 않은 클래스에는 매개변수 없는 공개 생성자가 자동으로 있습니다.

필드 선언 형태
[어노테이션...] [수식어...] 타입 이름 [= 초기값];
class Config {
public static final int MAX_RETRY = 3;
public static int created = 0;
public const string region = "kr";
private final string id;
private int count = 0;
string? note;
public Config(string id) {
this.id = id;
}
}
  • 필드는 타입을 명시합니다. var 필드는 Field declaration must use an explicit type. 오류입니다.
  • 한 줄에 여러 필드(int a, b;)를 선언할 수 없습니다.
  • static final 필드는 선언 시 초기값이 필요합니다 (Static final field 'MAX' must have an initializer.).
  • 초기값 없는 final 인스턴스 필드는 모든 생성자에서 정확히 한 번 대입해야 합니다 (Final field 'a' must be assigned in constructor 'P'.). 이후 대입은 Cannot assign to final field 'id'. 오류입니다.
  • static 메서드에서 인스턴스 필드를 사용하면 Cannot access instance field 'count' from static context. 오류입니다.
  • 정적 필드는 Config.created처럼 클래스 이름으로 접근합니다.
생성자 선언 형태
[수식어...] 클래스이름(매개변수...) {
본문
}
  • 이름이 클래스 이름과 같고 반환 타입이 없습니다. 반환 타입이 없는 다른 이름의 메서드는 Method declaration must include a return type. 오류입니다.
  • 매개변수와 필드 이름이 같으면 this.name = name;처럼 필드에 this.를 붙입니다.
  • 매개변수 목록이 다르면 생성자를 여러 개 선언할 수 있습니다. 생성자에서 다른 생성자(this(...))나 부모 생성자(super(...))를 호출하는 문법은 없습니다.
  • 객체 생성은 new 없이 Config("a")로 씁니다(생성자 호출).
메서드 선언 형태
[어노테이션...] [수식어...] 반환타입 이름(매개변수...) {
본문
}
public int add(int left, int right) {
return left + right;
}
static void log(string message) {
print(message);
}
  • 반환 타입은 타입 이름, void, var 중 하나입니다. 반환 타입이 void가 아니면 모든 종료 경로에서 값을 반환해야 합니다 (Method 'f' must return int.). 예외를 던지는 경로는 반환이 없어도 됩니다.
  • 오버로드: 매개변수 타입 목록이 다르면 같은 이름의 메서드를 여러 개 선언할 수 있습니다. 반환 타입만 다른 선언은 Duplicate method signature 'f(int)' in class 'P'. 오류입니다. native 실행 대상에서는 같은 이름의 메서드·생성자가 매개변수 개수로 구분되어야 하며, 개수가 같으면 Native overload requires distinct parameter arity 오류입니다.
  • 본문 없이 ;로 끝나는 메서드는 native 메서드에서만 사용할 수 있습니다.
  • 다음은 미지원입니다: 제네릭 메서드(<T> T id(T x)), 식 본문(int f() => 1;), 기본값 매개변수(int a = 1), 가변 인수, throws 절.
  • 같은 클래스의 메서드는 helper()처럼 이름만으로, 정적 메서드는 Main.helper()처럼 클래스 이름으로도 호출할 수 있습니다.
매개변수 형태
타입 이름 // 일반 매개변수
{required 타입 이름, ...} // 중괄호로 감싼 이름 있는 매개변수 목록
  • void 매개변수는 Method parameter must not use void type. 오류입니다. 같은 이름의 매개변수와 지역 변수를 함께 선언할 수 없습니다.
  • 중괄호 목록과 required는 UI 위젯 생성자에서 쓰는 이름 있는 매개변수 표기입니다. 기본값이 없으므로 모든 매개변수를 전달해야 합니다.
전체 소스: Cards.uni
class Card {
public string title;
public int order;
public Card({required string title, required int order}) {
this.title = title;
this.order = order;
}
}
class Cards {
static void main() {
Card card = Card(order: 1, title: "Intro");
print(card.title + " " + card.order);
}
}

모든 메서드·생성자 호출에서 이름: 값 형태로 인수를 전달할 수 있습니다.

규칙 위반 시 진단
인수 개수는 매개변수 개수와 같아야 함(기본값 없음) No matching constructor … / No matching method …
이름 있는 인수는 순서를 바꿀 수 있음 —
위치 인수를 먼저, 이름 있는 인수를 나중에 Positional argument cannot follow named arguments.
이름은 실제 매개변수 이름 Unknown named argument 'nick'.
같은 이름을 두 번 전달할 수 없음 Duplicate named argument 'x'.
전체 소스: Pairs.uni
class Pair<K, V> {
public K key;
public V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
}
interface Source<T> {
T next();
}
class Ones implements Source<int> {
public int next() {
return 1;
}
}
class Pairs {
static void main() {
Pair<string, int> pair = Pair<string, int>("a", 1);
Source<int> source = Ones();
print(pair.getKey() + source.next());
}
}
  • 타입 매개변수는 클래스·인터페이스에만 선언합니다. 이름은 식별자만 쓸 수 있고 제약(<T extends X>)은 없습니다.
  • 정적 멤버에서는 타입 매개변수를 사용할 수 없습니다 (Static member must not use type parameter 'T'.).
  • 제네릭 타입은 타입 인수를 모두 지정합니다. List xs는 Generic type 'List' expects 1 type argument(s) but got 0. 오류입니다. var는 타입 인수로 쓸 수 없습니다. void는 반환값 없는 함수와 Future<void>에서 사용합니다.
  • 타입 인수가 다르면 서로 다른 타입입니다. Box<int>를 Box<long>에 대입할 수 없습니다.
타입 값 비고
bool (boolean) true, false 두 이름은 같은 타입
byte, short, int, long 정수 리터럴은 int, L 접미사는 long
float, double 실수 리터럴은 double, f 접미사는 float
char ASCII 문자 하나 'a'
string 문자열 "text", 소문자 string
void 반환값 없음 메서드 반환 타입에만 사용

decimal, uuid, datetime은 데이터 모델·DB 연동용 타입 이름입니다. 이 타입에는 리터럴이 없고 int·null 대입도 거절되므로 일반 계산에는 사용하지 마세요. 날짜·시간 값은 DateTime.now()가 반환하는 DateTime 클래스를 사용합니다.

Rect rect = Rect(2.0, 3.0);
List<string> names = ["Kim", "Lee"];
Set<int> ids = {1, 2};
Map<string, int> ages = {"Kim": 30};
Map<string, List<int>> groups = {"a": [1, 2]};

배열 타입(int[])과 튜플 타입은 없습니다. 함수 타입은 (int) -> string처럼 작성합니다. 여러 값은 List<T>를 사용합니다. 컬렉션 리터럴은 식과 연산자를 참고하세요.

타입 뒤의 ?는 null을 넣을 수 있는 타입입니다. 제네릭 인수에도 사용할 수 있습니다(List<string?>).

string? note = null; // 허용
string name = "Kim";
note = name; // string → string? 허용
코드 진단
string s = null; Cannot assign null to string variable 's'.
string s = note; (note가 string?) Cannot assign string? to string variable 's'.
var s = null; Cannot infer type for variable 's' from initializer 'null'.
note + "x" Operator '+' cannot be applied to string? and string.

지역 변수·매개변수는 if (note != null)의 참인 분기에서 string으로 사용할 수 있습니다. if (note == null) { return; } 뒤에도 같은 블록에서는 null이 아닌 것으로 좁힙니다. &&·||의 오른쪽과 삼항 연산자의 분기에도 대응하는 null 확인이 적용됩니다.

필드·메서드 결과, 다시 대입하는 변수, 람다 바깥의 검사, 반복문 조건과 !(note == null) 같은 부정식에는 이 좁히기를 적용하지 않습니다. 필드는 지역 변수로 복사한 뒤 검사하세요.

확인하지 않은 nullable 값의 메서드 호출은 지역 변수 초기값, text.length(); 같은 호출 문장, 인수, 피연산자, return 값과 print에서 Cannot use member 'length' of nullable string? value 'text'; check it for null first. 오류입니다. map.get(key).size()처럼 표준 라이브러리의 nullable 결과에도 적용되므로 결과를 지역 변수에 받은 뒤 확인합니다. item.name처럼 nullable 객체의 필드를 읽는 경우는 아직 거절하지 않으므로 직접 null을 확인하세요.

좁히기는 타입 검사에만 영향을 줍니다. 모든 변환 대상은 숫자·bool·string·객체·컬렉션의 T?를 각 언어의 null 표현으로 생성합니다(예: Rust Option, Go 포인터, Python is None 검사). HTTP 전달 경계의 nullable 지원은 대상별 계약을 확인하세요.

List<string?>처럼 nullable 요소도 사용할 수 있습니다. 현행 8개 서버 생성 경로는 nullable 요소를 포함한 DTO의 List 필드를 처리하며 JSON null 요소를 보존합니다. 요소 타입과 중첩 구조, 본문·목록 크기는 서버 프로젝트의 HTTP 경계를 따릅니다. 일반 메서드의 타입 지원과 HTTP 입력 검증은 구분해서 확인하세요.

int에서 long·double, long에서 double로의 확대는 변수·필드 초기값, 대입, 반환값, 메서드·생성자 인수에서 자동으로 적용됩니다. 축소는 변환 메서드로 명시합니다.

코드 결과
long l = 42; 허용 (int → long)
double d = 42; 허용 (int → double)
double d = 42L; 허용 (long → double)
int i = 42L; Cannot assign long to int variable 'i'.
byte b = 1;, short s = 1; Cannot assign int to byte variable 'b'. — 상수여도 축소하지 않음
float f = 1.5; Cannot assign double to float variable 'f'. — 1.5f를 사용

확대를 명시하려면 int.toLong(), int.toDouble(), long.toDouble()을 호출합니다. long.toLong(), double.toDouble()처럼 같은 타입으로 바꾸는 호출은 거절됩니다. long.toInt(), double.toInt(), double.toLong()으로 축소합니다. 결과 타입의 범위 안에서는 double의 소수 부분을 0 방향으로 버립니다. 범위 밖 값·NaN·무한대는 대상별로 오류·값 유지·overflow 등의 결과가 다르며 native는 오류로 처리합니다. (int)value cast 문법은 없습니다.

long은 Go에서 int64, TypeScript에서 number로 생성합니다. 모든 대상에서 같은 정수 값을 보장하는 범위는 −9,007,199,254,740,991부터 9,007,199,254,740,991까지이며, 그 밖의 정밀도와 overflow는 대상 언어의 규칙을 따릅니다. DB·HTTP 경계 타입의 지원은 스칼라 대입 지원과 별도로 확인합니다.

var 이름 = 초기값;은 초기값의 타입으로 변수 타입을 정합니다. 이후 다른 타입을 대입하면 Cannot assign string to int target 'v'. 오류입니다. var는 지역 변수에만 사용하며 필드에는 사용할 수 없습니다. null, [], {}처럼 타입을 알 수 없는 초기값에는 타입을 명시하세요.