关于文件读写库的设计,也许可以引入Object Capability

不像js或者c,moonbit这种静态类型且类型安全的语言,有这样一种特性:特定的某个对象是无法伪造的。所以可以考虑使用object capability的形式实现权限管理。简单的说,就是“如果你没有某个令牌对象的引用,就没法使用它所对应的权限”。

大致思路如下,以简单的文件读写为例:

1、在模块1中创建一个抽象类型File,作为文件读写权限的令牌,提供以下api:

pub fn get_file_from_path(path : String) -> File? {
  ... // 调用模块内部私有的ffi函数
}

pub fn read(self : File) -> String
pub fn write(self: File, text : String) -> Unit

2、在模块2中引入并导出模块1中的所有内容,除了创建File的函数

3、定义安全模块:没有直接或间接依赖模块1的模块。

于是安全模块中只能读写 由不安全模块创建的文件。

同样的手段可以用来表示“获取File对象的权限”,即get_file_from_path这个函数对象本身。以及“获取get_file_from_path这个对象的权限”。

权限的细化也可以通过定义新的抽象类型或者闭包实现:

pub fn readonly(self : File) -> () -> String {
  fn() { self.read() }
}

或者像参考资料1那样。moonbit里可以用trait object。

除了文件库,任何需要管理权限的都可以参照这种形式,详见参考资料。


一些参考资料:

再举个有意思的例子,可撤销的权限:

type CancelAble[A, B] Ref[((A) -> B)?]

pub fn new[A, B](cap : (A) -> B) -> (CancelAble[A, B], () -> Unit) {
  let ref = Ref::new(Some(cap))
  fn cancel() { ref.value = None }
  (ref, cancel)
}

pub fn use[A, B](self : CancelAble[A, B], arg : A) -> B? {
  match self._ {
    None => None
    Some(cap) => Some(cap(arg))
  }
}

pub fn valid(self : CancelAble[A, B]) -> Bool {
  self._.value != None
}

由于CancelAble在模块外是抽象的,所以使用者没法把CancelAble里的权限偷出来接着用

重新设计1楼中文件读写的模块,可以得到一个比较有实际意义的例子:

删除原先的get_file_from_path,新增一个抽象类型Dir,并提供以下接口:

pub fn get_file(self : Dir, file_name : String) -> File!Error

pub fn get_dir(self : Dir, file_name : String) -> Dir!Error

pub fn make_dir(self : Dir, subdir_name : String) -> Dir

pub fn make_dir_abs(self : RootCapability, path : String) -> Dir

如果语言能支持设置main函数带有一个RootCapability类型的参数,且某个应用程序的main长这样:

fn main(root : RootCapability) {
  let my_dir = root.make_dir_abs("xxx")
  let config_file = my_dir.get_file!("config")
  let cache_dir = my_dir.get_dir!("cache")
  
  app_begin(config_file, cache_dir)
}

仅通过查看main函数,就能证明这个应用程序没有访问其它文件或目录的权限。