Skip to content

nocopy 结构体 ​

为什么需要 ​

普通结构体可以随意复制。但如果结构体里装的是文件句柄、网络连接这类只能关闭一次的资源,复制就会出问题:两个副本都以为自己拥有同一个文件,各自关闭一次——第二次关掉的可能是程序后来打开的另一个文件。

用 bu_kaobei(不可拷贝 / nocopy)修饰的结构体禁止复制,并且可以定义一个销毁前自动执行的清理方法。

基本写法 ​

cpro
wailian hs close(fd z32) z32

bu_kaobei jiegouti File {
    fd leixing z32,
}

shixian File:f {
    hs xiaohui_qian() kongbai {
        bu_anquan { close(f.fd) }
    }
}
cpro
外联 函数 close(fd i32) i32

不可拷贝 结构体 File {
    fd 类型 i32,
}

实现 File:f {
    函数 销毁前() void {
        不安全 { close(f.fd) }
    }
}
cpro
extern fn close(fd i32) i32

nocopy struct File {
    fd type i32,
}

impl File:f {
    fn before_destroy() void {
        unsafe { close(f.fd) }
    }
}

xiaohui_qian(销毁前 / before_destroy)是固定的方法名:没有参数,返回类型是 kongbai(void)。变量离开作用域时,编译器会自动调用它:

cpro
hs work() kongbai {
    bl f leixing File = { fd: 3 }
    // 使用 f
}                       // 这里自动调用清理方法,文件被关闭
cpro
函数 work() void {
    变量 f 类型 File = { fd: 3 }
    // 使用 f
}                       // 这里自动调用清理方法,文件被关闭
cpro
fn work() void {
    var f type File = { fd: 3 }
    // 使用 f
}                       // 这里自动调用清理方法,文件被关闭

不能手动调用这个方法。

  • 只写 bu_kaobei 不写清理方法也可以,那就只是一个不能复制的类型
  • 普通结构体不能定义清理方法,否则它仍然能被复制,问题依旧

能怎么用 ​

nocopy 的值不能复制,也不能移动,只能这样用:

cpro
bl f leixing File = { fd: 3 }   // ✓ 创建
bl g = f                        // ✗ 不能复制
bl h = yidong f                 // ✗ 不能移动
cpro
变量 f 类型 File = { fd: 3 }    // ✓ 创建
变量 g = f                      // ✗ 不能复制
变量 h = 移动 f                 // ✗ 不能移动
cpro
var f type File = { fd: 3 }     // ✓ 创建
var g = f                       // ✗ 不能复制
var h = move f                  // ✗ 不能移动

让别的函数用一下,借给它:

cpro
hs read_line(f &File) zifuchuan {
    fanhui "……"
}

rukou() {
    bl f leixing File = { fd: 3 }
    dayin(read_line(&f))        // ✓ 借用,f 仍然归自己
}
cpro
函数 read_line(f &File) string {
    返回 "……"
}

入口() {
    变量 f 类型 File = { fd: 3 }
    打印(read_line(&f))         // ✓ 借用,f 仍然归自己
}
cpro
fn read_line(f &File) string {
    return "……"
}

rukou() {
    var f type File = { fd: 3 }
    print(read_line(&f))        // ✓ 借用,f 仍然归自己
}

让函数创建一个返回出来,必须在 fanhui 处直接构造:

cpro
hs open_it(n z32) File {
    fanhui { fd: n }            // ✓ 直接构造
}

hs open_bad(n z32) File {
    bl t leixing File = { fd: n }
    fanhui t                    // ✗ 不能返回局部变量
}
cpro
函数 open_it(n i32) File {
    返回 { fd: n }              // ✓ 直接构造
}

函数 open_bad(n i32) File {
    变量 t 类型 File = { fd: n }
    返回 t                      // ✗ 不能返回局部变量
}
cpro
fn open_it(n i32) File {
    return { fd: n }            // ✓ 直接构造
}

fn open_bad(n i32) File {
    var t type File = { fd: n }
    return t                    // ✗ 不能返回局部变量
}

按值传参同样不行,请改成借用:

cpro
hs take(f File) kongbai { }     // ✗
hs take(f &File) kongbai { }    // ✓
cpro
函数 take(f File) void { }      // ✗
函数 take(f &File) void { }     // ✓
cpro
fn take(f File) void { }        // ✗
fn take(f &File) void { }       // ✓

什么时候自动清理 ​

以下几种情况都会自动调用清理方法:

  • 正常执行到作用域结束
  • 提前 fanhui
  • 错误向外传播时(sp_kecuo,或在 buhuo 里返回错误)
  • 给变量重新赋值时,先清理旧值

同一作用域里有多个 nocopy 变量时,按声明的相反顺序清理。

程序 panic 时不会清理,进程直接结束,由操作系统回收资源。

清理方法里的限制 ​

清理方法里可以读写字段、调用普通函数,但不能把自己或字段借出去,包括调用自己的其他方法:

cpro
shixian File:f {
    hs flush() kongbai { }

    hs xiaohui_qian() kongbai {
        f.flush()                   // ✗ 不能调用自己的其他方法
        bu_anquan { close(f.fd) }   // ✓
    }
}
cpro
实现 File:f {
    函数 flush() void { }

    函数 销毁前() void {
        f.flush()                   // ✗ 不能调用自己的其他方法
        不安全 { close(f.fd) }      // ✓
    }
}
cpro
impl File:f {
    fn flush() void { }

    fn before_destroy() void {
        f.flush()                   // ✗ 不能调用自己的其他方法
        unsafe { close(f.fd) }      // ✓
    }
}

清理动作请直接写在清理方法里。

重要数据要显式关闭 ​

自动清理时如果出错(比如磁盘满了),错误会被静默忽略——清理方法没有返回值,也没有地方报错。

所以写入重要数据时,请调用资源自带的关闭函数并检查结果,例如文件模块的 fs.close(&f),见 fs 文件。自动清理负责"不泄漏",显式关闭负责"确认成功"。

当前不支持 ​

nocopy 类型目前不能用在这些地方:

  • 普通结构体的字段(nocopy 结构体自己的字段可以是任何类型)
  • 数组元素
  • optional
  • 闭包捕获
  • 全局变量
  • dayin 打印
  • xiaohui 提前销毁——请用资源自带的关闭函数

cpro 目前处于测试阶段