Appearance
所有权、借用与移动
本文档记录 KCP 的所有权前置语义:显式借用、显式移动、like const 转发、移动引用、特殊成员函数,以及这些能力当前能否作为 concept 约束使用。引用和指针的基础类型规则见 type_system.md,结构体构造、析构和成员函数见 struct.md。
设计目标
KCP 默认保持普通值语义:
cp
let b = a; // copy资源所有权转移必须显式写出:
cp
let b = move a;第一版不采用 Rust 的“非 Copy 类型默认 move”,也不照搬 C++ 的 T&& / std::forward<T> 语法。语言区分四类明确操作:
text
copy: x
borrow: ref x / const ref x
move: move x
forward: forward x语法总览
text
Type -> TypeBase TargetQualifier? TypeSuffix
TargetQualifier -> const | like | forward
TypeSuffix -> *+ &?
| &
| move &
| forward &
BorrowExpr -> ref Expression
| const ref Expression
MoveExpr -> move Expression
ForwardExpr -> forward Expression
ConstExpr -> const Expression
SpecialMember -> CopyConstructor
| CopyAssignment
| MoveConstructor
| MoveAssignment
DeletedMember -> SpecialMember = delete ;
DefaultedMember -> Constructor = default ;move 和 ref 在表达式位置是前缀操作。ref 已经用于局部声明中的引用 binding,例如 let ref alias = value;;表达式级 ref value 是同一词汇的扩展。
类型
引用和移动引用类型写作:
cp
T&
T const&
T like*
T like**
T like*&
T like&
T move&
T forward&含义:
T&是可写借用。T const&是只读借用。T like*、T like**、T like*&、T like&是 receiver-const 传播类型,具体由当前self like&receiver 决定。T move&是移动借用,表示被调用方可以接管资源。T forward&是泛型转发引用,只能用于依赖类型或省略参数类型引入的隐藏类型参数。
T move& 是独立于 C++ T&& 的简化设计:
- 普通左值不能自动绑定到
T move&。 move x、临时值和函数返回值可以绑定到T move&。- 没有引用折叠。
- 第一版不支持
T const move&。
T forward& 是独立于 C++ T&& 的显式转发设计:
T forward&可绑定可写左值、const 左值、临时值、函数返回值和move x。- 命名的
T forward&参数在函数体内按左值使用。 - 继续传递原始值类别必须写
forward value。 T forward&...值参数包展开时,每个 pack 元素独立记录自己的转发类别;template for中绑定到该元素的局部名也可以继续写forward name。- 同一个泛型函数以可写左值、const 左值和右值调用时生成不同实例签名,避免把
T&、T const&和T move&混用。函数体语义检查使用物化后的参数类型:可写左值调用中的T forward&参数是T&,const 左值调用中是T const&,临时值、函数返回值和move x调用中是T move&。但是forward value的合法性仍来自源码参数是否写成forward&,普通T&、T const&或T move&参数即使物化形状相同,也不是 forward-capable binding。 forward只能形成T forward&,不能形成T forward*、T forward**或T forward*&。T forward&的被引用类型必须是依赖类型,或来自省略参数类型引入的隐藏类型参数。i32 forward&、[i32; 2] forward&这类具体类型转发引用会报invalid_type_argument。self forward&当前不支持;即使 parser 能读到这种 receiver 形态,语义层也会报self forward& is not supported。需要转发 receiver 值类别时,当前应改成普通显式泛型参数或按self&/self const&/self move&分别声明。
move& 和 forward& 都不能和 const 或 like 组合。因此 T const move&、T like move&、T const forward& 和 T like forward& 都不是当前合法类型。
T like 是为成员函数和 concept requirement 准备的窄规则。它只传播 receiver 的 target constness,不传播 move,不推导基础类型,也不表示任意类型限定:
cp
operator [](self like&, index: usize) -> T like&
{
return ref data[index];
}
data(self like&) -> T like*
{
return ptr;
}上面的声明等价于同时提供:
cp
operator [](self&, index: usize) -> T&
operator [](self const&, index: usize) -> T const&规则:
like只允许出现在成员函数、operator 或 concept requirement 中。- 使用
like的函数必须有且只有一个self like&receiver。 - 同一个函数签名中的所有
like都绑定到这个 receiver。 self like&可以接受可写 receiver 和 const receiver。- receiver 是可写时,每个
like后缀按可写目标物化:T like*视为T*,T like&视为T&。 - receiver 是 const 时,每个
like后缀按 const 目标物化:T like*视为T const*,T like&视为T const&。 - 多级指针和指针引用也按同一规则递归物化。
T like**在可写 receiver 下是可写的双重指针视图;在 const receiver 下,穿过两级解引用得到的T是只读目标,**object.data()不能赋值。T like*&返回的是随 receiver constness 物化后的指针字段引用;const receiver 下该引用是只读视图,不能通过它改写字段或最终目标。 like不接受self move&,也不会产生T move&。- 函数体按可写 receiver 视图和 const receiver 视图分别检查;如果 const 视图下发生写入,则该
like函数非法。
冒号后继续永远写类型:
cp
read(value: T const&)
edit(value: T&)
at(self like&, index: usize) -> T like&
data(self like&) -> T like*
take(value: T move&)不引入 read(const ref value: T)、edit(ref value: T) 或 take(move value: T) 这类参数声明语法。这样参数、返回类型、函数类型和类型别名保持同一套类型语法:
cp
type reader = f(T const&) -> i32;
front(self&) -> T&;显式借用表达式
表达式级借用写作:
cp
ref value
const ref value规则:
ref expr要求expr是左值。若expr是可写左值,结果类型是T&;若expr是 const 左值,结果类型是T const&。const ref expr要求expr是左值,结果类型是T const&。- 可写左值上的
ref expr可以匹配T&参数,也可以退化匹配T const&参数。 - const 左值上的
ref expr结果已经是T const&,只能匹配只读引用目标。 ref expr不能匹配按值T参数。const ref expr只能匹配T const&参数。
示例:
cp
foo_value(value: i32) {
}
foo_ref(value: i32&) {
}
foo_const_ref(value: i32 const&) {
}
main()
{
let x = 1;
foo_value(x); // copy
foo_ref(ref x); // borrow
foo_const_ref(ref x); // writable borrow to readonly borrow
foo_const_ref(const ref x);
// foo_value(ref x); // error: explicit borrow does not copy
}对 const 左值写 ref 不会去掉只读性:
cp
const value = 1;
foo_const_ref(ref value); // 合法:结果是 i32 const&
// foo_ref(ref value); // 错误:不能把 const 左值借成 i32&显式 ref 不会隐式读出并复制为值。需要传值时直接写原表达式;需要转移资源时写 move expr。
返回引用也使用同一套显式借用表达式:
cp
front(self&) -> T&
{
return ref data[0];
}return (x); 不表示返回引用;括号只做普通分组。
显式移动表达式
移动写作:
cp
move value规则:
move expr要求expr是可写左值。move expr的结果类型是T move&。move expr可以匹配T move&参数。move expr可以匹配按值T参数,此时使用T的 move 构造产生参数值。move expr不匹配T&或T const&参数。move expr本身只改变表达式值类别,不立即调用 move constructor,也不销毁源对象。 只有当它流向按值局部初始化、按值传参、按值返回、construct_at的 value、 或其它需要构造一个T值的目标时,才按目标类型选择 move 构造;流向T move&参数时只是绑定移动借用。赋值表达式target = move source则按operator =候选选择 move assignment,找不到可用 operator 时才走当前内建赋值路径。
当前实现的 move 是显式值类别标记和 copy/move constructor 选择信号,还没有接入 use-after-move 状态检查。因此:
- 对同一个 binding 多次写
move value,语义层不会因为“已经 move 过”而报错。 move value之后继续读取字段、调用方法或再次按值传递,语义层仍按普通表达式检查。- 具体类型的 move constructor 可以把源对象置为某个有效但类型自定义的状态;例如标准库
stringmove 后当前运行时测试期望源串为空,但这是该类型实现约定,不是语言级 moved-state 规则。 - 需要在 API 边界禁止赋值,应优先使用 deleted
operator =并依赖调用点诊断;需要禁止显式构造,可以删除对应 copy/move constructor。当前 deleted copy/move constructor 还不能完整禁止所有隐式按值 copy/move,详细边界见“特殊成员能力与 concept 边界”。不能依赖当前编译器做通用 use-after-move 分析。
命名的 T move& 参数在函数体内按左值使用。如果需要继续转移,必须再次写 move:
cp
take<T>(value: T move&) -> T
{
return move value;
}这不是完美转发,只是显式继续移动。
显式转发表达式
转发写作:
cp
forward value规则:
forward expr只允许作用于 forward-capable binding。- 操作数必须解析到一个 forward-capable binding 的名字本身;允许普通括号分组,但不允许转发字段访问、下标访问、调用结果或任意表达式。
- forward-capable binding 包括当前函数的
forward&参数,以及template for从T forward&...值参数包元素建立的局部绑定。 - 普通局部变量、按值参数、
T...值参数包展开变量、T&.../T const&.../T move&...展开变量都不是 forward-capable binding。 - 如果该 binding 由左值绑定,
forward value表现为普通可写左值。 - 如果该 binding 由 const 左值绑定,
forward value表现为只读左值。 - 如果该 binding 由临时值、函数返回值或
move绑定,forward value表现为move value。 - 当前没有通用 moved-state 检查;多次
forward value的语义约束来自目标参数类型和具体 copy/move 构造可用性。需要继续保留原始右值类别时仍必须显式写forward value。
cp
relay<T>(value: T forward&)
{
consume(forward value);
}显式 const 表达式
const value 是表达式级只读视图:
- 不复制、不移动、不借用,只把表达式视为 const。
- 保留原值类别;左值仍是左值,临时值仍是临时值。
const ref value仍表示显式 const borrow;const value不是 borrow,可以匹配按值参数。const (move value)和move (const value)非法。
return local; 满足 NRVO 条件时直接返回该 local 的存储位置,不是隐式 move。return move local; 保持显式移动语义,并明确不触发 NRVO。完整 NRVO 条件见 type_system.md 的返回值消除规则。
默认 copy 和显式 move
普通赋值和按值传参默认使用 copy:
cp
let b = a;
consume(a);如果类型不可 copy,以上写法报错。用户必须写:
cp
let b = move a;
consume(move a);按值参数的匹配规则:
- 实参是普通表达式时,要求目标类型可 copy。
- 实参是
move expr时,要求目标类型可 move。 - 实参是
ref expr或const ref expr时,不匹配按值参数。
特殊成员函数
结构体拥有以下特殊成员:
text
default constructor
destructor
copy constructor
copy assignment
move constructor
move assignmentcopy 构造和 move 构造使用构造函数语法表达:
text
impl handle {
handle(other: this const&) = delete;
handle(other: this move&);
}copy 赋值和 move 赋值使用 operator = 表达:
text
impl handle {
operator =(self&, rhs: this const&) = delete;
operator =(self&, rhs: this move&) -> this&;
}当前实现规则:
- 默认构造函数:没有任何用户声明构造函数时,
T{}走逐字段默认初始化;所有省略字段都可默认初始化时可用,否则报默认初始化失败。用户写了T() = default;时也表示同一逐字段默认初始化路径,但它是构造候选中的一个符号,deleted 或签名错误时按普通构造函数规则诊断。 - 析构函数:当前没有为无用户析构函数的
struct生成可调用析构函数符号;这类类型在清理路径上等价于 no-op。用户写了~T()后使用用户版本。 - copy constructor:当前不会合成一个可枚举的默认 copy constructor 符号。显式构造表达式
T(value)会按普通构造候选打分;如果选中的是 deleted 构造函数,语义层报not_callable,并且不会退回字段聚合初始化。按值初始化、传参、返回、字段写入和 range-for 按值绑定中,如果源和目标的读出类型是同一个struct,且存在非 deleted 的单参数引用构造函数T(other: this const&)/T(other: this&),当前会使用第一个匹配声明;如果源是 const,只能匹配this const&,如果源不是 const,则this&和this const&都可匹配,实际使用声明顺序中的第一个非 deleted 匹配项。deleted copy constructor 目前只稳定阻止显式构造候选;隐式按值 copy 可能跳过 deleted 构造函数。如果没有可用用户构造函数,当前走内建同类型值复制。 - move constructor:同样不会合成默认 move constructor 符号。显式
T(move value)选到 deleted move constructor 时按 deleted constructor 报错;但move value流向按值目标时,只寻找非 deleted 的单参数T(other: this move&)用户构造函数,并使用第一个匹配声明。deleted move constructor 目前只稳定阻止显式构造候选;隐式按值 move 可能跳过 deleted 构造函数。如果没有可用用户构造函数,当前走内建同类型值写入。this const move&不是合法类型,普通左值也不会隐式选择 move 构造。语言层还没有递归检查“所有字段可 move construct”并据此隐式删除整个类型。 - copy / move assignment:赋值表达式先按普通 operator 重载查找
operator =。用户声明的operator =(self&, rhs: this const&)或operator =(self&, rhs: this move&)会作为候选参与选择,deleted 候选会让调用点报错。没有可用operator =时,当前退回到内建赋值:右侧只要能按目标类型隐式转换即可写入左侧;它不是一个可枚举的默认 assignment 符号,也不会逐字段调用用户自定义 assignment。
例如:
cp
struct box {
value: i32;
}
impl box {
box(other: this const&) = delete;
}
main() -> i32
{
let first = box{ .value = 42 };
let second = first; // 当前仍可走内建同类型复制
return second.value;
}上面这类 deleted copy constructor 只稳定禁止显式选择该构造候选的写法,例如 let second = box{first}; 会报 deleted constructor 不可调用。它还不能作为“这个类型不可复制”的完整语言承诺;需要阻止 left = right 这类赋值时,应删除对应 operator =,而不是只删除 copy constructor。
= delete 是局部能力开关,不是属性,也不依赖继承。在所有权语义里它主要用于 copy/move 构造、copy/move 赋值和需要显式禁止的赋值 operator。普通 deleted impl 函数见 struct.md,deleted operator 的候选选择规则见 operator.md。
析构与清理路径
局部变量和按值参数会按作用域形成清理路径。当前实现只为非引用对象中可解析到 struct 析构函数的 binding 注册清理;引用 binding、裸指针、storage T{} 的原始槽位本身不触发被指向对象或槽内对象析构。
清理规则:
- 普通 block 结束时,按局部对象构造/绑定的逆序调用析构函数。
return离开当前函数前,会先清理当前作用域中仍然存活的局部对象。break和continue跳出或进入下一轮循环前,会清理从当前位置到目标循环边界之间的局部对象。- 按值函数参数和按值参数包元素会进入函数入口清理栈;引用参数和
self&这类引用 receiver 不注册清理。 - 解构声明中,按值绑定的每个非引用元素分别拥有自己的清理;
let ref (a, b) = tuple;只绑定引用,不析构原 tuple 的元素。 for(let value : range)的按值循环变量按每一轮迭代注册和清理;break/continue会先清理当前迭代中已经注册的对象,再跳到目标 block。for(let ref value : range)不注册循环变量析构。template for不是运行时循环;每次展开后的普通语句按当前展开作用域注册清理。template for(let value : values...)的展开变量只是对应参数包元素的当前作用域名字,不额外 copy 构造一个新对象,也不单独注册析构;按值参数包元素的清理由函数入口参数 binding 负责。展开体正常结束时清到该展开开始前;展开体里return、break或continue按对应目标清理。template if只实例化被选中的分支;被选中分支形成一个清理子作用域,未选中分支不注册任何清理。- NRVO 返回的 local 是函数返回对象本身,callee 中不再对这个 returned local 调用析构;调用方接收后的对象按调用方生命周期析构。
- 手动
construct_at到storage或裸指针中的对象,必须由用户配对destroy_at。覆盖或离开storagebinding 不会自动析构槽内对象。 - 当前自动清理不会递归析构数组、tuple 或
variant内部元素。需要递归析构数组元素时使用destroy_at(ptr_to_array);variantpayload 的按 tag 析构分发尚未接入。 static局部变量不进入普通局部清理栈,也没有程序退出时的析构顺序保证。
特殊成员能力与 concept 边界
当前编译器已经在构造、赋值和清理路径中使用 copy/move 特殊成员可用性,但这些能力还没有作为可导入、可约束的公开 concept 暴露。
因此:
- 没有标准库或语言内建的
copyable、movable、move_onlyconcept。 - 语义层不会把
T: movable解释为“检查 move constructor 和 move assignment 可用”。如果没有用户自己声明并导入名为movable的普通 concept,它就是未知 concept。 - 即使用户声明了同名普通 concept,编译器也不会自动根据特殊成员函数给类型生成实现证明;仍需按普通 concept impl 规则提供
impl movable for Type。 - 当前
requires也没有not约束,因此不能直接用movable and not copyable表达 move-only。 - 需要在当前实现中禁止赋值时,使用 deleted
operator =,它会参与 operator 候选选择并在被选中时报错。deleted copy/move 构造函数当前只稳定阻止显式构造候选;隐式按值 copy/move 可能跳过 deleted 构造函数并落回内建同类型复制/写入,所以不要把它当作完整 move-only 类型系统。需要在泛型 API 中约束这种能力时,只能依赖已有普通 concept 或让具体调用点实例化后暴露特殊成员不可用的诊断。
第一版边界
第一版支持:
- 默认 copy。
T&、T const&、T like*、T like&、T move&和T forward&。ref expr、const ref expr、move expr、forward expr和const expr。= delete用于 copy/move 特殊成员,也可用于显式禁止的 impl 函数和 operator;所有权文档只展开特殊成员部分。
第一版不支持:
- 引用折叠。
- C++ 风格
T&&/std::forward<T>。 like传播 move。- 具体类型 forward 引用,例如
i32 forward&;forward&要求 pointee 是依赖类型,或来自省略参数类型的隐式泛型参数。 T const move&。T const forward&。self forward&。- 默认非 Copy 类型自动 move。
- 把
ref expr隐式复制成按值参数。 - 以属性或继承语法声明 move-only。
本文档不定义标准库所有权类型、动态缓冲区、容器或底层分配接口。它只给这些设计提供语言前提。