首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  程序员

Go2 设计草案介绍

  •  
  •   songtianyi · 2018-08-29 20:19:39 +08:00 · 5353 次点击
    这是一个创建于 417 天前的主题,其中的信息可能已经有所发展或是发生改变。

    写下自己的理解。大家都在吐槽泛型,我觉得还行吧,contract 的表达能力蛮强的。

    原文链接 https://github.com/songtianyi/songtianyi.github.io/blob/master/mds/techniques/go2-design-draft-introduction.md

    go2 设计草案介绍

    作者: songtianyi 2018-08-29

    前言

    Go,毫无疑问已经成为主流服务端开发语言之一,但它的类型特性却少的可怜,仅支持structural subtyping。在 TIOBE 排名前二十的语言中,不管是上古语言 Java, 还是 2010 年之后出现的新语言 Rust/Julia 等,都支持至少三种类型特性,对此社区抱怨很多,另外还有它的错误处理方式,以及在 Go1.11 版本才解决的依赖管理等问题。在最近的 GopherCon2018 上,官方放出了解决这些问题的草案(draft),这些内容还没有成为正式的提案(proposal), 只是先发出来供大家讨论,最终会形成正式提案并被逐步引入到后续的版本中。此次放出的草案,集中讨论了三个问题,泛型 /错误处理 /错误值。

    泛型

    泛型是复用逻辑的一个有效手段,在 2016 和 2017 年的 Go 语言调查中,泛型都列在最迫切的需求之首,在 Go1.0 release 之后 Go team 就已经开始探索如何引入泛型,但同时要保持 Go 的简洁性(开发者喜爱 Go 的主要原因之一),之前的几种实现方式都存在严重的问题,被废弃掉了,所以进展并不算快,甚至导致部分人误解为 Go team 并不打算引入泛型。现在,最新的草案经过半年的讨论和优化,已经确认可行(could work),我们期待已久的泛型几乎是板上钉钉的事情了,那么 Go 的泛型大概长什么样?

    在没有泛型的情况下,通过interface{}是可以解决部分问题的,比如ring的实现,但这种方法只适合用在数据容器里, 且需要做类型转换。当我们需要实现一个通用的函数时,就做不到了,例如实现一个函数,其返回传入的 map 的 key:

    package main
    
    import "fmt"
    
    func Keys(m map[interface{}]interface{}) []interface{} {
    	keys := make([]interface{}, 0)
    	for k, _ := range m {
    		keys = append(keys, k)
    	}
    	return keys
    }
    
    func main() {
    	m := make(map[string]string, 1)
    	m["demo"] = "data"
    	fmt.Println(Keys(m))
    }
    

    这样写连编译都通过不了,因为类型不匹配。那么参考其他支持泛型的语言的语法,可以这样写:

    package main
    
    import "fmt"
    
    func Keys<K, V>(m map[K]V) []K {
    	keys := make([]K, 0)
    	for k, _ := range m {
    		keys = append(keys, k)
    	}
    	return keys
    }
    
    func main() {
    	m := make(map[string]string, 1)
    	m["demo"] = "data"
    	fmt.Println(Keys(m))
    }
    

    但是这种写法是有缺陷的,假设 append 函数并不支持 string 类型,就可能会出现编译错误。我们可以看下其他语言的做法:

    // rust
    fn print_g<T: Graph>(g : T) {
        println!("graph area {}", g.area());
    }
    

    Rust 在声明 T 的时候,限定了入参的类型,即入参 g 必须是 Graph 的子类。和Rust的 nominal subtyping 不同,Go 属于 structural subtyping,没有显式的类型关系声明,因此不能使用此种方式。Go 在草案中引入了contract来解决这个问题,语法类似于函数, 写法更复杂,但表达能力比 Rust 要更强:

    // comparable contract
    contract Equal(t T) {
    	t == t
    }
    // addable contract
    contract Addable(t T) {
    	t + t
    }
    

    上述代码分别约束了 T 必须是可比较的(comparable),必须是能做加法运算(addable)的。使用方式很简单, 定义函数的时候加上约束即可:

    func Sum(type T Addable(T))(x []T) T {
    	var total T
    	for _, v := range x {
    		total += v
    	}
    	return total
    }
    
    var x []int
    total := Sum(int)(x)
    

    得益于类型推断,在调用 Sum 时可以简写成:

    total := Sum(x)
    

    contract 在使用时,如果参数是一一对应的(可推断), 也可以省略参数:

    func Sum(type T Addable)(x []T) T {
    	var total T
    	for _, v := range x {
    		total += v
    	}
    	return total
    }
    

    不可推断时就需要指明该 contract 是用来约束谁的:

    func Keys(type K, V Equal(K))(m map[K]V) []K {
    	...
    }
    

    当然,下面的写法也可以推断,最终如何就看 Go team 的抉择了:

    func Keys(type K Equal, V)(m map[K]V) []K {
    	...
    }
    

    关于实现方面的内容,这里不再讨论,留给高手吧。官方开通了反馈渠道,可以去提意见,对于我来说,唯一不满意的地方是显式的type关键字, 可能是为了方便和后边的函数参数相区分吧。

    错误处理

    健壮的程序需要大量的错误处理逻辑,在极端情况下,错误处理逻辑甚至比业务逻辑还要多,那么更简洁有效的错误处理语法是我们所追求的。

    先看下目前 Go 的错误处理方式,一个拷贝文件的例子:

    func CopyFile(src, dst string) error {
    	r, err := os.Open(src)
    	if err != nil {
    		return fmt.Errorf("copy %s %s: %v", src, dst, err)
    	}
    	defer r.Close()
    
    	w, err := os.Create(dst)
    	if err != nil {
    		return fmt.Errorf("copy %s %s: %v", src, dst, err)
    	}
    
    	if _, err := io.Copy(w, r); err != nil {
    		w.Close()
    		os.Remove(dst)
    		return fmt.Errorf("copy %s %s: %v", src, dst, err)
    	}
    
    	if err := w.Close(); err != nil {
    		os.Remove(dst)
    		return fmt.Errorf("copy %s %s: %v", src, dst, err)
    	}
    }
    

    上述代码中,错误处理的代码占了总代码量的接近 50%!

    Go 的assignment-and-if-statement错误处理语句是罪魁祸首,草案引入了check表达式来代替:

    r := check os.Open(src)
    

    但这只代替了赋值表达式和 if 语句,从之前的例子中我们可以看到,有四行完全相同的代码:

    return fmt.Errorf("copy %s %s: %v", src, dst, err)
    

    它是可以被统一处理的, 于是 Go 在引入check的同时引入了handle语句:

    handle err {
    		return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    

    修改后的代码为:

    func CopyFile(src, dst string) error {
    	handle err {
    		return fmt.Errorf("copy %s %s: %v", src, dst, err)
    	}
    
    	r := check os.Open(src)
    	defer r.Close()
    
    	w := check os.Create(dst)
    	handle err {
    		w.Close()
    		os.Remove(dst) // (only if a check fails)
    	}
    
    	check io.Copy(w, r)
    	check w.Close()
    	return nil
    }
    

    check 失败后,先被执行最里层的(inner most)的 handler,接着被上一个(按照语法顺序)handler 处理,直到 handler 执行了return语句。

    Go team 对该草案的期望是能够减少错误处理的代码量, 且兼容之前的错误处理方式, 要求不算高,这个设计也算能接受吧。

    反馈渠道

    错误值

    Go 的错误值目前存在两个问题。一,错误链(栈)没有被很好地表达;二,缺少更丰富的错误输出方式。在该草案之前,已经有不少第三方的 package 实现了这些功能,现在要进行标准化。目前,对于多调用层级的错误,我们使用 fmt.Errorf 或者自定义的 Error 来包裹它:

    package main
    
    import (
    	"fmt"
    	"io"
    )
    
    type RpcError struct {
    	Line uint
    }
    
    func (s *RpcError) Error() string {
    	return fmt.Sprintf("(%d): no route to the remote address", s.Line)
    }
    
    func fn3() error {
    	return io.EOF
    }
    
    func fn2() error {
    	if err := fn3(); err != nil {
    		return &RpcError{Line: 12}
    	}
    	return nil
    }
    func fn1() error {
    	if err := fn2(); err != nil {
    		return fmt.Errorf("call fn2 failed, %s", err)
    	}
    	return nil
    }
    func main() {
    	if err := fn1(); err != nil {
    		fmt.Println(err)
    	}
    }
    

    此程序的输出为:

    call fn2 failed, (12): no route to the remote address
    

    很明显的问题是,我们在 main 函数里对 error 进行处理的时候不能进行类型判断, 比如使用 if 语句判断:

    if err == io.EOF { ... }
    

    或者进行类型断言:

    if pe, ok := err.(*os.PathError); ok { ... pe.Path ... }
    

    它是一个 RpcError 还是 io.EOF? 无从知晓。一大串的错误信息,人类可以很好地理解,但对于程序代码来说就很困难。

    error inspection

    草案引入了一个 error wrapper 来包裹错误链, 它相当于一个指针,将错误栈链接起来:

    package errors
    
    // A Wrapper is an error implementation
    // wrapping context around another error.
    type Wrapper interface {
    	// Unwrap returns the next error in the error chain.
    	// If there is no next error, Unwrap returns nil.
    	Unwrap() error
    }
    

    每个层级的 error 都实现这个 wrapper,这样在 main 函数里,我们可以通过 err.Unwrap() 来获取下一个层级的 error。另外,草案引入了两个函数来简化这个过程:

    // Is reports whether err or any of the errors in its chain is equal to target.
    func Is(err, target error) bool
    
    // As checks whether err or any of the errors in its chain is a value of type E.
    // If so, it returns the discovered value of type E, with ok set to true.
    // If not, it returns the zero value of type E, with ok set to false.
    func As(type E)(err error) (e E, ok bool)
    
    error formatting

    有时候我们需要将错误信息分类,因为某些情况下你需要所有的信息,某些情况下只需要部分信息,因此草案引入了一个 interface:

    package errors
    
    type Formatter interface {
    	Format(p Printer) (next error)
    }
    

    error 类型可以实现 Format 函数来打印更详细的信息:

    func (e *WriteError) Format(p errors.Printer) (next error) {
    	p.Printf("write %s database", e.Database)
    	if p.Detail() {
    		p.Printf("more detail here")
    	}
    	return e.Err
    }
    
    func (e *WriteError) Error() string { return fmt.Sprint(e) }
    

    在你使用fmt.Println("%+v", err)打印错误信息时,它会调用 Format 函数。

    反馈渠道

    第 1 条附言  ·  2018-08-30 11:08:07 +08:00
    有些小的描述改进,可以点击查看原文链接。
    第 2 条附言  ·  2018-08-31 17:04:54 +08:00
    有小部分内容描述有误,已更正,请查看原文链接。
    73 回复  |  直到 2018-11-16 11:50:39 +08:00
        1
    kernel   2018-08-29 21:51:58 +08:00   ♥ 1
    我大 JS 2 空格党表示缩进 4 个已经不爽了 go 缩 8 个。。。
        2
    loqixh   2018-08-29 22:35:12 +08:00
    Go,毫无疑问已经成为主流服务端开发语言之一 谁给的自信............
        3
    songtianyi   2018-08-29 22:56:30 +08:00   ♥ 1
    @loqixh 现在 Go 职位蛮多的,这么说没毛病吧。
        4
    songtianyi   2018-08-29 22:57:14 +08:00   ♥ 1
    @kernel 1 个 tab, 看平台怎么渲染的了。
        5
    wenzhoou   2018-08-29 23:04:24 +08:00 via Android
    主流是形容服务端的。再说了,还有之一嘛!
        6
    Kilerd   2018-08-29 23:22:43 +08:00
    唯一一个用 tab 做缩进的语言,而且还是规范中强制。

    难受
        7
    mrcode   2018-08-29 23:24:21 +08:00
    @loqixh 认同楼上,谁给的自信让你加上“之一“的,分明是:毫无疑问已经成为主流服务端开发语言
        8
    glues   2018-08-29 23:39:50 +08:00
    这错误处理,还不如 go 1.x,虽然 1.x 已经够恶心的了
        9
    kidlj   2018-08-29 23:59:26 +08:00 via iPhone
    写得很好,清晰明了。感谢。
        10
    blless   2018-08-30 01:22:57 +08:00 via Android
    @loqixh 看讨论就知道 明显现在很多了,我司用 go 重构了几个 python 小服务之后就开始爆发了
        11
    kran   2018-08-30 01:35:45 +08:00 via iPhone
    一对比 1 的错误处理没那么差嘛😄
    感谢楼主,写的好
        12
    loqixh   2018-08-30 08:39:06 +08:00
    @blless 来来来, 说说市场占有率, 配得上主流二字, 少说也得 1%? 现在有 0.1%不?
        13
    BBCCBB   2018-08-30 08:39:38 +08:00
    有理有据,令人信服
        14
    artandlol   2018-08-30 09:09:27 +08:00
    @loqixh docker 容器的 都是 golang
        15
    natscat   2018-08-30 09:15:00 +08:00
    @loqixh #12 现在滴滴头条好多 golang 了吧 不说七牛这种公司 golang 在国内真的挺热的(此处不讨论这语言咋样)
        16
    loqixh   2018-08-30 09:29:48 +08:00   ♥ 1
    @artandlol 能拿出手的就一个 docker 功能都是 linux 系统实现的, go 只当了个粘合层, docker 啥语言都能写, 只是凑巧用了 go 而已...按你这方法 ruby 肯定是比 go 主流得多的后端语言 一个 gitlab 不知道把 go 秒到哪里去了
        17
    loqixh   2018-08-30 09:31:35 +08:00
    @natscat 然而除了你说的这几家其它就没有了, 而且这几家也只是系统少部分用了 go
        18
    encro   2018-08-30 09:36:48 +08:00
    改不如不改。有多丑啊这是。
        19
    natscat   2018-08-30 09:47:09 +08:00
    @loqixh #17 https://studygolang.com/topics/3685 当然不可能和 C++ Java 那样 毕竟 C++ Java 都好长时间了 golang 用的公司真的不少
        20
    natscat   2018-08-30 09:51:12 +08:00
    TiDB 这个是 PingCAP 做的 是 go; codis 原来豌豆荚做的 也是 golang https://github.com/hackstoic/golang-open-source-projects
        21
    natscat   2018-08-30 09:51:51 +08:00
    国内一线互联网公司都有用到 golang
        22
    loveCoding   2018-08-30 09:55:33 +08:00
    非常多的公司都在使用 golang 啊,尤其是基础服务相关的组件
        23
    artandlol   2018-08-30 09:55:33 +08:00
    @loqixh 杠精吗 谷歌的 kubernets 了解下 云服务行业主流的 docker 编排系统就是 kubernets。懂什么叫杀手级应用吗?
    gitlab 就我在用,在 kubernets 上跑过 gitlab-ci 吗?
        24
    loqixh   2018-08-30 09:57:30 +08:00
    @artandlol 算了 你们自嗨吧.....
        25
    natscat   2018-08-30 10:00:43 +08:00
    @loqixh #24 不了解别人和你说了 就说别人自(。・∀・)ノ゙嗨。。。(讨论的结果)
        26
    HowToMakeLove   2018-08-30 10:04:28 +08:00
    rust 里没有类的概念吧?
    `Rust 在声明 T 的时候,限定了入参的类型,即入参 g 必须是 Graph 的子类。`
        27
    loqixh   2018-08-30 10:10:02 +08:00
    @natscat 我是杠精我闭嘴, 你们随意
        28
    zichen   2018-08-30 10:15:43 +08:00
    docker,kubernates,tidb,istio,geth,go 在基础架构和中间件这一层,王牌项目已经挺多的了。
        29
    blless   2018-08-30 10:16:17 +08:00 via Android
    @loqixh 1%我不敢说 0.1%我还真觉得有
        30
    micean   2018-08-30 10:16:48 +08:00
    互联网的服务端应用吧……要是在传统企业应用里写各种变态的逻辑不难受吗?
        31
    duanquanyong   2018-08-30 10:28:41 +08:00
    v2ex 真是个大杂烩
        32
    nino   2018-08-30 10:43:02 +08:00   ♥ 2
    这么有营养的帖子但是回复都超没营养
        33
    glues   2018-08-30 10:45:14 +08:00   ♥ 2
    @artandlol Linux 是 C 写的,按你们这逻辑,C 在服务端开发占有率已经超过 90% 了?
        34
    chai2010   2018-08-30 10:48:56 +08:00
    @nino #32 这是 V2EX 的特色,有人和你抬杠,就是不和你讨论技术
        35
    songtianyi   2018-08-30 10:51:36 +08:00
        36
    myyou   2018-08-30 10:52:47 +08:00
    @glues 对啊,c 就是占用那么高啊,你以为服务端是一门语言决定啊? c 占 90%其它语言就站 10%啊?
        37
    glues   2018-08-30 11:00:05 +08:00
    @myyou 既然是这么算的话,那 go 确实连 0.1% 都占不到啊,没毛病啊
        38
    songtianyi   2018-08-30 11:04:23 +08:00
    @HowToMakeLove 很多时候 英文里的概念 翻译成中文之后就会让人产生这种疑惑,习惯就好。
        39
    songtianyi   2018-08-30 11:05:50 +08:00
    @HowToMakeLove 改成 子类型 ,你可能就好接受点了。
        40
    artandlol   2018-08-30 11:20:30 +08:00
    @glues 胖虎你是不是 @ 错人了
        41
    vigoss   2018-08-30 11:32:52 +08:00
    这个泛型的写法也太复杂了吧。

    ps 帮配音 @glues: "怎么了我胖虎说的有毛病吗?"
        42
    glues   2018-08-30 11:54:02 +08:00
    @artandlol 我胖虎想 @ 谁就 @ 谁 (逃。。
        43
    Biebe   2018-08-30 12:09:39 +08:00 via iPhone
    @loqixh gitlab 不只有一个 rails,其余中间件如 gitaly shell 都是 go 写的
        44
    ifaii   2018-08-30 12:28:26 +08:00 via iPhone
    最大黑点就是错误处理 简直不能再烂
        45
    jlkm2010   2018-08-30 12:43:48 +08:00   ♥ 2
    还有人在这里杠,就足以说明 go 是主流语言之一了
        46
    rahuahua   2018-08-30 12:49:53 +08:00
    @loqixh 可以找各个大公司的招聘看看,基本上都有 go 的职位,如果这都不算主流的话,那只有 c/c++/java 才能算主流了
        47
    simpx   2018-08-30 14:39:10 +08:00
    go2 的风格,有从 c 变成 c++的趋势
        48
    cchange   2018-08-30 15:22:40 +08:00
    看来把 C++中不复杂的内容学通了就可以了 语言这些太多包袱了
        49
    karllynn   2018-08-30 15:30:02 +08:00
    屏蔽了某个杠精,神清气爽

    我觉得这个改动可以啊,contract 给一个最简单的泛型,够用了
        50
    iRiven   2018-08-30 16:05:27 +08:00 via Android
    泛型不太懂,看了官方的包各种类型判断,贼爽,至于错误处理还行,不是很理想,但有好过没有,后面的错误是扩展在 errors 包里还是直接在默认 error 类型上面扩展一个函数
        51
    rockyou12   2018-08-30 16:15:56 +08:00
    泛型就学人家用尖括号定义真的不行吗……原版的 map 和 slice 的类型写着我都感觉挺难受了……

    而且错误处理虽然没有 try/catch 的多层嵌套,但读的时候却是和业务顺序是反的,感觉好不适应,估计也只能多写一些菜知道好不好了……
        52
    songtianyi   2018-08-30 16:50:46 +08:00
    @iRiven 可以说说哪里不懂嘛?
        53
    songtianyi   2018-08-30 17:00:04 +08:00
        54
    janxin   2018-08-30 17:09:14 +08:00 via iPad
    这次的 handle 明显就是一种 try catch 机制老的,就是嵌套关系十分不明显,后续程序代码理解时就难受了
        55
    psuwgipgf   2018-08-30 17:42:36 +08:00
    @janxin 嗯,我也感觉不如 try catch 好用,可能是因为我是个 android 开发吧。不知道 其它流行语言是怎么处理这种情况的
        56
    songtianyi   2018-08-30 19:31:41 +08:00
    @psuwgipgf try catch 是主流,go 的做法有点像 rust,但类型系统比不上 rust,所以看起来蹩脚
        57
    songtianyi   2018-08-30 19:38:23 +08:00
    @janxin 的确,有一种 goto 的感觉,debug 和读起来比较费劲。
        58
    iceheart   2018-08-30 22:07:54 +08:00 via Android
    check handle 一点也不简洁。
    check 完全可以省掉。
    比如这样
    func service1()(string,error){}
    func example() error {
    handle error1 {
    .....
    }
    res1, error1 := service1()//error != nil 时 触发 error1
    ...
    }
        59
    janxin   2018-08-31 06:21:16 +08:00 via iPad
    @iceheart 最大的问题应该还是 go1 代码兼容问题。基于目前的方案,go1 的代码可以直接用在 go2 上,并且编译器无需大改。
        60
    janxin   2018-08-31 06:23:41 +08:00 via iPad
    @iceheart 而且还不需要考虑变量命名问题 error2 怎么办,比如可能出现的作用域问题等等
        61
    iceheart   2018-08-31 06:47:24 +08:00 via Android
    @janxin
    没懂你的意思
    看代码
    r, err := os.Open(src)
    变成草案:
    handle err { return fmt.Errorf("copy %s %s: %v", src, dst, err) }
    r := check os.Open(src)
    或者我说的
    handle err { return fmt.Errorf("copy %s %s: %v", src, dst, err) }
    r,err := os.Open(src)
    哪个看起来更简洁?
    反正我是很讨厌在每次函数调用加上个 check 前缀,这太啰嗦了
        62
    janxin   2018-08-31 08:17:11 +08:00 via iPad
    @iceheart
    R, err := os.Open(src)
    ...
    go func(){
    err:=validate(something)
    ...
    }()
    这样需要额外改动编译器支持作用域变更

    然后还有关于 Go1 老代码拿过来用时兼容问题:原有的
    if err != nil {
    ...
    }
    基本上都变成了冗余代码

    check 和原有写 err 并没有什么本质区别,一个冗余了 err,一个冗余了 check,不过使用关键字相当于显示化标示了此处有处理细节,你仍旧在 Go2 中可以使用 Go1 的语法。如果只是使用 err,如果没有定义 handle,是按照 Go1 原有的变量未使用错误还是按照默认的 handle 处理呢?
        63
    iceheart   2018-08-31 08:42:25 +08:00 via Android
    @janxin 不明白你说的作用域问题在哪里
    相对于 新草案的
    r := check os.Open(src)
    比较一下这个
    r,err := os.Open(src)
    哪个更兼容 GO1 ?
    没有定位 handle,后边这个不就是 Go1 的语法么?
    定义了 handle 的情况,这个明确指定了 handle 过程组,难道不更好吗?
        64
    janxin   2018-08-31 09:10:39 +08:00 via iPad
    @iceheart 但是如果 err 后续不使用在 go1 里编译报错的啊…
        65
    abscon   2018-08-31 11:34:28 +08:00 via iPhone
    @songtianyi 你回复 @HowToMakeLove 说的『改成「子类型」』也完全不能接受。Rust 里既没有类又没有子类型。生命周期倒是粘点边,可严格说来它也不是。

    Rust 也能约束泛型参数『必须是可比较的(comparable),必须是能做加法运算(addable)的』。

    另外你说的那个 Equal 在 Rust 里不叫『可比较的』,而是『存在等价关系』。而且还能区分出『部分等价关系』和『等价关系』。『可比较的』是另外的特性,也区分了『偏序』和『全序』。而且『可做加法运算』还能指定加法右边的类型。

    现在,该是你解释一下为什么『 Go2 的泛型约束的表达能力比 Rust 的 Trait 更强大』了。
        66
    iceheart   2018-08-31 12:33:05 +08:00 via Android
    @janxin Go1 编译 Go2 代码?
        67
    janxin   2018-08-31 12:55:34 +08:00 via iPad
    @iceheart 你这样编译器行为就改变了,现在只是新增,不一样的
        68
    janxin   2018-08-31 12:57:21 +08:00 via iPad
    @iceheart 本质上 go 是想保证原来的行为的基础上通过新增语法解决问题,而不是改变行为
        69
    iceheart   2018-08-31 13:20:10 +08:00 via Android
    @janxin 我的意思就是省掉 check, 让用户写代码不用那么恶心,改变啥行为了? 难道加个 handle 就不改变 Go1 的行为,就能编译了?
        70
    janxin   2018-08-31 14:53:00 +08:00 via iPad
    @iceheart 你不考虑 corner case 那就讨论不下去了
        71
    songtianyi   2018-08-31 16:56:38 +08:00
    @abscon 感谢指正,subtype 只存在于 lifetime 之间,我的描述有误。Rust 可以通过实现 trait 来约束。实现 Add trait 的时候 另外一个 operand 可以指定类型。比 Rust trait 的表达能力强的说法不对,只能说相对简单一些吧。
        72
    songtianyi   2018-08-31 17:04:04 +08:00
    @abscon @HowToMakeLove 已更正为: 即入参 g 必须是实现了 Graph 的类型
        73
    AmorZhang   338 天前
    @loqixh 没用过 就不要发言了吧
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1415 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 29ms · UTC 23:58 · PVG 07:58 · LAX 16:58 · JFK 19:58
    ♥ Do have faith in what you're doing.