Curiously recurring generic trait pattern: overflow evaluating the requirement

前端 未结 2 1480
太阳男子
太阳男子 2021-01-26 14:03

I am trying to implement a generic structure with a bunch of fields, where each of the field types should know about the exact type of the whole structure. It\'s a sort of strat

2条回答
  •  执笔经年
    2021-01-26 15:07

    If the following impl is characteristic for Strategy, then it might be parameterized on the wrong thing. (I'm going to ignore the associated type for this answer, because the example doesn't use it.)

    impl> Strategy for ExampleStrat {
        fn run(&self, e: &E) {
            let _ = e.data();
            // uses ExampleData here
        }
    }
    

    You could instead parameterize Strategy over D -- breaking the impl dependency cycle -- and parameterize only the run method over E.

    pub trait Strategy {
        fn run(&self, &impl HasData);
    }
    
    impl Strategy for ExampleStrat {
        fn run(&self, e: &impl HasData) {
            let _ = e.data();
            // uses ExampleData here
        }
    }
    

    fn run>(&self, e: &E) is another way to define run that is the same for this purpose. Here is a full example.

    A potential drawback of this approach is that run can't be called through a Strategy trait object, because it has to be monomorphized for any type that implements HasData. But the HasData trait doesn't seem to do much in this impl: the only thing it can do is return an internal reference, and once you have it, there's no point in using it again. Maybe run could just take a &D reference?

    pub trait Strategy {
        fn run(&self, &D);
    }
    
    impl Strategy for ExampleStrat {
        fn run(&self, _: &ExampleData) {
            // uses ExampleData here
        }
    }
    

    To be sure, now you have to call self.s.run(self.data()) in do_it, but this doesn't cost you in flexibility over the original version, in which, had it worked¹, you could only call Strategy::run with an argument of type &E.

    In fact, the whole HasData trait seems unnecessary to me: it's always implemented by the same type whose implementation calls it, so aside from the minor convenience of passing self instead of self.data, it doesn't elevate the level of abstraction inside the do_it method. So it seems to me effectively the same thing to delete HasData entirely and let Example know how to call Strategy::run with the right reference; it has to, anyway. (However, it's possible I merely lack imagination.)

    Any of these solutions ought to handle adding an associated type to Strategy, but without knowing how it will be used, it's hard to say for sure.

    ¹It could be made to work in some future version of the compiler, with sufficiently smart type checking.

提交回复
热议问题