rust泛型过度使用思考

写这篇文章的时候我只是用打印函数地址的方式来测试的,这个测试方法可能并不是很妥当,可能会引起编译器在某些情况下放弃优化(个人猜测),目前想到的方法:应该在release模式下大量impl一个实现了静态方法的trait看文件膨胀的速度,这个我目前还在想办法测试。所以目前来说下面的结论暂时还是存疑的,希望大家也能想办法测试或者能直接找到官方的实际做法的文章。

目前了解来看rust泛型的概念很好,比c++多了很多类型限定和编译时检查,出错提示相对c++的模板来说也特别特别的友好。但任何事物总是有两面性,过度的使用泛型会使得代码极度的膨胀,这个膨胀的速度在我看来比c++的模板快多了

1 trait里的默认函数和方法引起的代码膨胀

首先来看一段简单的代码:


trait Foo{
    fn foo(){
        println!("foo");
    }
}

struct Bar1;
struct Bar2;

impl Foo for Bar1{}
impl Foo for Bar2{}

fn main(){
    println!("{:p}",&Bar1::foo);
    println!("{:p}",&Bar2::foo);
}

看看这个运行结果:

0x7f8147412150
0x7f8147412178

结果可以看到:Bar1::foo和Bar2::foo是两个地址并不一样的函数,这样就意味着虽然这两个函数的二进制代码完全一样,但是rust还是给复制了两份。
从rust语言本身的特性来看这么做有他的理由,比如默认函数可以被覆盖等,但从结果上来看你就必须很小心了,如果你的trait Foo有很多默认函数,并且不巧你有很多类型都实现了Foo,那么代码的膨胀速度估计会出乎意料的

2 保存closure引起的代码膨胀

rust的closure与c++类似,都是生成一个匿名的结构体包装你用到的“环境”里所有的值,然后类似c++重载operator ()来实现调用,这也就意味着两个完全相同的closure,比如:|i|i+100 ,你写两次他就是两个不同的类型,这样你用泛型保存closure的时候就要注意了

下面看一个稍微复杂一点的例子:


trait Foo{
    fn foo(){
        println!("addr of Foo::foo:{:p}",&Self::foo);
    }

    fn print_foo_addr(&self);
}

//保存closure
struct Bar<F:Fn(i32)->i32>{
    func:F,
    i:i32,
}

impl <F> Bar<F> where F:Fn(i32)->i32{
    fn new(f:F)->Bar<F>{
        Bar{
            func:f,
            i:100
        }
    }

    fn print_new_addr(&self){
        println!("addr of Bar::new :{:p}",&Self::new);
        println!("addr of Bar::print :{:p}",&Self::print_new_addr);
    }   
}

impl<F>Foo for Bar<F> where F:Fn(i32)->i32 {
    fn print_foo_addr(&self){
        Self::foo();
        println!("addr of Foo::print:{:p}",&Self::print_foo_addr);
    }
}

fn main(){
    let b = Bar::new(|i|{
        i+100
    });

    let b2 = Bar::new(|i|{
        i+100
    });

    b.print_foo_addr();
    b.print_new_addr();
    println!("");
    b2.print_foo_addr();
    b2.print_new_addr();
}

Play地址:http://is.gd/CM8Vnj
无论是debug,release,nightly输出结果都差不多:

addr of Foo::foo:0x7ffbe66e5180
addr of Foo::print:0x7ffbe66e5190
addr of Bar::new :0x7ffbe66e51a0
addr of Bar::print :0x7ffbe66e51b0

addr of Foo::foo:0x7ffbe66e51c0
addr of Foo::print:0x7ffbe66e51d0
addr of Bar::new :0x7ffbe66e51e0
addr of Bar::print :0x7ffbe66e51f0

可以看到所有的方法的代码全部被复制了一遍

如果你只是用某个函数或者方法接收一个closure做回调那就好很多了,rust不是整个类型的全部函数方法都复制,rust只会把你接收closure的那个函数重新实现一下

附带一个列子强化下即使看起来一样的closure都是不一样的:

struct Foo<F:Fn(i32)->i32>{
    f:F,
}


impl<F:Fn(i32)->i32> Foo<F>{
    fn new(f:F)->Foo<F>{
        Foo{
            f:f,
        }
    }
}


fn main(){

    let b = Foo::new(|i|{i+100});
    (b.f)(100);
}

上面这个例子保存了一个closure,编译和运行都正常,play地址:http://is.gd/JVCd00

我们对其做一些修改,再添加一个成员:f2:F


struct Foo<F:Fn(i32)->i32>{
    f:F,
    f2:F,
}

impl<F:Fn(i32)->i32> Foo<F>{
    fn new(f:F,f2:F)->Foo<F>{
        Foo{
            f:f,
            f2:f2,
        }
    }
}

fn main(){

    let b = Foo::new(|i|{i+100},|i|{i+100});
    (b.f)(100);
    (b.f2)(100);
}

Play地址:http://is.gd/GSTQbq
这个列子是不能正确编译的的,看看编译结果:

<anon>:20:33: 20:43 error: mismatched types:
 expected `[closure <anon>:20:22: 20:32]`,
    found `[closure <anon>:20:33: 20:43]`
(expected closure,
    found a different closure) [E0308]
<anon>:20     let b = Foo::new(|i|{i+100},|i|{i+100});
                                          ^~~~~~~~~~
note: in expansion of closure expansion
<anon>:20:33: 20:43 note: expansion site
<anon>:20:33: 20:43 help: see the detailed explanation for E0308
<anon>:20:33: 20:43 note: no two closures, even if identical, have the same type
<anon>:20     let b = Foo::new(|i|{i+100},|i|{i+100});
                                          ^~~~~~~~~~
note: in expansion of closure expansion
<anon>:20:33: 20:43 note: expansion site
<anon>:20:33: 20:43 help: consider boxing your closure and/or using it as a trait object
<anon>:20     let b = Foo::new(|i|{i+100},|i|{i+100});
                                          ^~~~~~~~~~
note: in expansion of closure expansion
<anon>:20:33: 20:43 note: expansion site
error: aborting due to previous error
playpen: application terminated with error code 101
Compilation failed.

也就是说我们看起来两个完全一样的closure编译器认为是两种类型

3 lifetime参数并不会引起代码的复制

lifetime也放在了<>里,测试下看看:

struct Foo<'a>{
    i:&'a i32,
}

impl<'a> Foo<'a>{

    fn print_addr(&self){
        println!("i={},{:p}",self.i,&Self::print_addr);
    }
}

fn main(){
    let i=1i32;
    let f1 = Foo{i:&i};
    f1.print_addr();
    {
        {
            let k=2i32;
            let f3 = Foo{i:&k};
            f3.print_addr();
        }
        let j=3i32;
        let f2 = Foo{i:&j};
        f2.print_addr();

        {
            let f4 = Foo{i:&i};
            f4.print_addr();
        }
    }
}

Play地址:http://is.gd/lepdI2
结果可以看出lifetime并不会引起代码的复制

i=1,0x7f7ca62b1140
i=2,0x7f7ca62b1140
i=3,0x7f7ca62b1140
i=1,0x7f7ca62b1140
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值