Mysterious lifetime issue while implementing trait for dyn object












8















Consider the following toy example:



use std::cmp::Ordering;

pub trait SimpleOrder {
fn key(&self) -> u32;
}

impl PartialOrd for dyn SimpleOrder {
fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for dyn SimpleOrder {
fn cmp(&self, other: &dyn SimpleOrder) -> Ordering {
self.key().cmp(&other.key())
}
}

impl PartialEq for dyn SimpleOrder {
fn eq(&self, other: &dyn SimpleOrder) -> bool {
self.key() == other.key()
}
}

impl Eq for SimpleOrder {}


This doesn't compile. It claims there is a lifetime issue in the implementation for partial_cmp:



error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> src/main.rs:9:23
|
9 | Some(self.cmp(other))
| ^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the method body at 8:5...
--> src/main.rs:8:5
|
8 | / fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
9 | | Some(self.cmp(other))
10| | }
| |_____^
note: ...so that the declared lifetime parameter bounds are satisfied
--> src/main.rs:9:23
|
9 | Some(self.cmp(other))
| ^^^^^
= note: but, the lifetime must be valid for the static lifetime...
= note: ...so that the types are compatible:
expected std::cmp::Eq
found std::cmp::Eq


I really don't understand this error. In particular "expected std::cmp::Eq found std::cmp::Eq" is puzzling.



If I inline the call manually it compiles fine:



fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Some(self.key().cmp(&other.key()))
}


What's going on here?










share|improve this question




















  • 1





    This is mysterious!

    – Peter Hall
    1 hour ago






  • 1





    Since we are talking about traits... 'static is probably missing somewhere?

    – Matthieu M.
    1 hour ago











  • @MatthieuM. Why is a static lifetime required for the argument of partial_cmp but not for cmp?

    – Peter Hall
    1 hour ago











  • @PeterHall: I have no idea, but I think that this may be the clue behind the "expected std::cmp::Eq found std::cmp::Eq", one has a 'static lifetime that is not shown, while the other doesn't. I am certainly looking forward to the answer of this question :D

    – Matthieu M.
    1 hour ago













  • fn partial_cmp(&self, other: &(dyn SimpleOrder + 'static)) -> Option<Ordering> works ;)

    – hellow
    1 hour ago


















8















Consider the following toy example:



use std::cmp::Ordering;

pub trait SimpleOrder {
fn key(&self) -> u32;
}

impl PartialOrd for dyn SimpleOrder {
fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for dyn SimpleOrder {
fn cmp(&self, other: &dyn SimpleOrder) -> Ordering {
self.key().cmp(&other.key())
}
}

impl PartialEq for dyn SimpleOrder {
fn eq(&self, other: &dyn SimpleOrder) -> bool {
self.key() == other.key()
}
}

impl Eq for SimpleOrder {}


This doesn't compile. It claims there is a lifetime issue in the implementation for partial_cmp:



error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> src/main.rs:9:23
|
9 | Some(self.cmp(other))
| ^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the method body at 8:5...
--> src/main.rs:8:5
|
8 | / fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
9 | | Some(self.cmp(other))
10| | }
| |_____^
note: ...so that the declared lifetime parameter bounds are satisfied
--> src/main.rs:9:23
|
9 | Some(self.cmp(other))
| ^^^^^
= note: but, the lifetime must be valid for the static lifetime...
= note: ...so that the types are compatible:
expected std::cmp::Eq
found std::cmp::Eq


I really don't understand this error. In particular "expected std::cmp::Eq found std::cmp::Eq" is puzzling.



If I inline the call manually it compiles fine:



fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Some(self.key().cmp(&other.key()))
}


What's going on here?










share|improve this question




















  • 1





    This is mysterious!

    – Peter Hall
    1 hour ago






  • 1





    Since we are talking about traits... 'static is probably missing somewhere?

    – Matthieu M.
    1 hour ago











  • @MatthieuM. Why is a static lifetime required for the argument of partial_cmp but not for cmp?

    – Peter Hall
    1 hour ago











  • @PeterHall: I have no idea, but I think that this may be the clue behind the "expected std::cmp::Eq found std::cmp::Eq", one has a 'static lifetime that is not shown, while the other doesn't. I am certainly looking forward to the answer of this question :D

    – Matthieu M.
    1 hour ago













  • fn partial_cmp(&self, other: &(dyn SimpleOrder + 'static)) -> Option<Ordering> works ;)

    – hellow
    1 hour ago
















8












8








8








Consider the following toy example:



use std::cmp::Ordering;

pub trait SimpleOrder {
fn key(&self) -> u32;
}

impl PartialOrd for dyn SimpleOrder {
fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for dyn SimpleOrder {
fn cmp(&self, other: &dyn SimpleOrder) -> Ordering {
self.key().cmp(&other.key())
}
}

impl PartialEq for dyn SimpleOrder {
fn eq(&self, other: &dyn SimpleOrder) -> bool {
self.key() == other.key()
}
}

impl Eq for SimpleOrder {}


This doesn't compile. It claims there is a lifetime issue in the implementation for partial_cmp:



error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> src/main.rs:9:23
|
9 | Some(self.cmp(other))
| ^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the method body at 8:5...
--> src/main.rs:8:5
|
8 | / fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
9 | | Some(self.cmp(other))
10| | }
| |_____^
note: ...so that the declared lifetime parameter bounds are satisfied
--> src/main.rs:9:23
|
9 | Some(self.cmp(other))
| ^^^^^
= note: but, the lifetime must be valid for the static lifetime...
= note: ...so that the types are compatible:
expected std::cmp::Eq
found std::cmp::Eq


I really don't understand this error. In particular "expected std::cmp::Eq found std::cmp::Eq" is puzzling.



If I inline the call manually it compiles fine:



fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Some(self.key().cmp(&other.key()))
}


What's going on here?










share|improve this question
















Consider the following toy example:



use std::cmp::Ordering;

pub trait SimpleOrder {
fn key(&self) -> u32;
}

impl PartialOrd for dyn SimpleOrder {
fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for dyn SimpleOrder {
fn cmp(&self, other: &dyn SimpleOrder) -> Ordering {
self.key().cmp(&other.key())
}
}

impl PartialEq for dyn SimpleOrder {
fn eq(&self, other: &dyn SimpleOrder) -> bool {
self.key() == other.key()
}
}

impl Eq for SimpleOrder {}


This doesn't compile. It claims there is a lifetime issue in the implementation for partial_cmp:



error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
--> src/main.rs:9:23
|
9 | Some(self.cmp(other))
| ^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the method body at 8:5...
--> src/main.rs:8:5
|
8 | / fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
9 | | Some(self.cmp(other))
10| | }
| |_____^
note: ...so that the declared lifetime parameter bounds are satisfied
--> src/main.rs:9:23
|
9 | Some(self.cmp(other))
| ^^^^^
= note: but, the lifetime must be valid for the static lifetime...
= note: ...so that the types are compatible:
expected std::cmp::Eq
found std::cmp::Eq


I really don't understand this error. In particular "expected std::cmp::Eq found std::cmp::Eq" is puzzling.



If I inline the call manually it compiles fine:



fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {
Some(self.key().cmp(&other.key()))
}


What's going on here?







rust traits lifetime






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 1 hour ago









Peter Hall

15.7k74186




15.7k74186










asked 1 hour ago









orlporlp

67.2k27160247




67.2k27160247








  • 1





    This is mysterious!

    – Peter Hall
    1 hour ago






  • 1





    Since we are talking about traits... 'static is probably missing somewhere?

    – Matthieu M.
    1 hour ago











  • @MatthieuM. Why is a static lifetime required for the argument of partial_cmp but not for cmp?

    – Peter Hall
    1 hour ago











  • @PeterHall: I have no idea, but I think that this may be the clue behind the "expected std::cmp::Eq found std::cmp::Eq", one has a 'static lifetime that is not shown, while the other doesn't. I am certainly looking forward to the answer of this question :D

    – Matthieu M.
    1 hour ago













  • fn partial_cmp(&self, other: &(dyn SimpleOrder + 'static)) -> Option<Ordering> works ;)

    – hellow
    1 hour ago
















  • 1





    This is mysterious!

    – Peter Hall
    1 hour ago






  • 1





    Since we are talking about traits... 'static is probably missing somewhere?

    – Matthieu M.
    1 hour ago











  • @MatthieuM. Why is a static lifetime required for the argument of partial_cmp but not for cmp?

    – Peter Hall
    1 hour ago











  • @PeterHall: I have no idea, but I think that this may be the clue behind the "expected std::cmp::Eq found std::cmp::Eq", one has a 'static lifetime that is not shown, while the other doesn't. I am certainly looking forward to the answer of this question :D

    – Matthieu M.
    1 hour ago













  • fn partial_cmp(&self, other: &(dyn SimpleOrder + 'static)) -> Option<Ordering> works ;)

    – hellow
    1 hour ago










1




1





This is mysterious!

– Peter Hall
1 hour ago





This is mysterious!

– Peter Hall
1 hour ago




1




1





Since we are talking about traits... 'static is probably missing somewhere?

– Matthieu M.
1 hour ago





Since we are talking about traits... 'static is probably missing somewhere?

– Matthieu M.
1 hour ago













@MatthieuM. Why is a static lifetime required for the argument of partial_cmp but not for cmp?

– Peter Hall
1 hour ago





@MatthieuM. Why is a static lifetime required for the argument of partial_cmp but not for cmp?

– Peter Hall
1 hour ago













@PeterHall: I have no idea, but I think that this may be the clue behind the "expected std::cmp::Eq found std::cmp::Eq", one has a 'static lifetime that is not shown, while the other doesn't. I am certainly looking forward to the answer of this question :D

– Matthieu M.
1 hour ago







@PeterHall: I have no idea, but I think that this may be the clue behind the "expected std::cmp::Eq found std::cmp::Eq", one has a 'static lifetime that is not shown, while the other doesn't. I am certainly looking forward to the answer of this question :D

– Matthieu M.
1 hour ago















fn partial_cmp(&self, other: &(dyn SimpleOrder + 'static)) -> Option<Ordering> works ;)

– hellow
1 hour ago







fn partial_cmp(&self, other: &(dyn SimpleOrder + 'static)) -> Option<Ordering> works ;)

– hellow
1 hour ago














1 Answer
1






active

oldest

votes


















9














Trait object types have an associated lifetime bound, but it can be omitted. A full trait object type is written dyn Trait + 'a (when behind a reference, parentheses must be added around it: &(dyn Trait + 'a)).



The problem is that when a lifetime bound is omitted, the compiler uses different rules to infer the lifetime based on the location where the trait object type is used.



First, we have:



impl PartialOrd for dyn SimpleOrder {


Here, the compiler implies + 'static. Lifetime parameters are never introduced on impl blocks (as of Rust 1.32.0).



Next, we have:



    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {


The type of other is inferred to be &(dyn SimpleOrder + 'a), where 'a is an implicit lifetime parameter introduced on partial_cmp.



    fn partial_cmp<'a>(&self, other: &(dyn SimpleOrder + 'a)) -> Option<Ordering> {


So now we have that self has type &(dyn SimpleOrder + 'static) while other has type &(dyn SimpleOrder + 'a). What's the problem?



Indeed, cmp doesn't give any error, because its implementation doesn't require that the lifetime of the two trait objects be equal. Why does partial_cmp care, though?



Because partial_cmp is calling Ord::cmp. When type checking a call to a trait method, the compiler checks against the signature from the trait. Let's review that signature:



pub trait Ord: Eq + PartialOrd<Self> {
fn cmp(&self, other: &Self) -> Ordering;


The trait requires that other be of type Self. That means that when partial_cmp calls cmp, it tries to pass a &(dyn SimpleOrder + 'a) to a parameter that expects a &(dyn SimpleOrder + 'static), because that's what Self is. This conversion is not valid, so the compiler gives an error.



So then, why is it valid to set the type of other to &(dyn SimpleOrder + 'a) when implementing Ord? Because &(dyn SimpleOrder + 'a) is a supertype of &(dyn SimpleOrder + 'static), and Rust lets you replace a parameter type with one of its supertypes when implementing a trait method (it makes the method strictly more general, even though it's apparently not used much in type checking).





In order to make your implementation as generic as possible, you could introduce a lifetime parameter on the impls:



use std::cmp::Ordering;

pub trait SimpleOrder {
fn key(&self) -> u32;
}

impl<'a> PartialOrd for dyn SimpleOrder + 'a {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl<'a> Ord for dyn SimpleOrder + 'a {
fn cmp(&self, other: &Self) -> Ordering {
self.key().cmp(&other.key())
}
}

impl<'a> PartialEq for dyn SimpleOrder + 'a {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}

impl<'a> Eq for dyn SimpleOrder + 'a {}





share|improve this answer





















  • 2





    That makes sense. I do believe that the error message generated by Rust should be improved though - that this is the issue isn't at all clear from the error message.

    – orlp
    48 mins ago











  • "So now we have that self has type &(dyn SimpleOrder + 'a) while other has type &(dyn SimpleOrder + 'static)." – this should be the other way round, right?

    – Chronial
    48 mins ago











  • @Chronial oops, fixed.

    – Francis Gagné
    47 mins ago






  • 3





    The thing that is surprising to me here is that calling SimpleOrder::cmp(self, other) does not check against the signature of SimpleOrder::cmp (which would succeed), but against the signature of Ord::cmp (which fails).

    – Chronial
    45 mins ago











  • @Chronial: I think this would warrant its own (separate) question; notably, I guess it would be possible to trigger the behavior without dyn Trait, just exploiting sub-typing with regular types containing references.

    – Matthieu M.
    17 mins ago











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54329200%2fmysterious-lifetime-issue-while-implementing-trait-for-dyn-object%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









9














Trait object types have an associated lifetime bound, but it can be omitted. A full trait object type is written dyn Trait + 'a (when behind a reference, parentheses must be added around it: &(dyn Trait + 'a)).



The problem is that when a lifetime bound is omitted, the compiler uses different rules to infer the lifetime based on the location where the trait object type is used.



First, we have:



impl PartialOrd for dyn SimpleOrder {


Here, the compiler implies + 'static. Lifetime parameters are never introduced on impl blocks (as of Rust 1.32.0).



Next, we have:



    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {


The type of other is inferred to be &(dyn SimpleOrder + 'a), where 'a is an implicit lifetime parameter introduced on partial_cmp.



    fn partial_cmp<'a>(&self, other: &(dyn SimpleOrder + 'a)) -> Option<Ordering> {


So now we have that self has type &(dyn SimpleOrder + 'static) while other has type &(dyn SimpleOrder + 'a). What's the problem?



Indeed, cmp doesn't give any error, because its implementation doesn't require that the lifetime of the two trait objects be equal. Why does partial_cmp care, though?



Because partial_cmp is calling Ord::cmp. When type checking a call to a trait method, the compiler checks against the signature from the trait. Let's review that signature:



pub trait Ord: Eq + PartialOrd<Self> {
fn cmp(&self, other: &Self) -> Ordering;


The trait requires that other be of type Self. That means that when partial_cmp calls cmp, it tries to pass a &(dyn SimpleOrder + 'a) to a parameter that expects a &(dyn SimpleOrder + 'static), because that's what Self is. This conversion is not valid, so the compiler gives an error.



So then, why is it valid to set the type of other to &(dyn SimpleOrder + 'a) when implementing Ord? Because &(dyn SimpleOrder + 'a) is a supertype of &(dyn SimpleOrder + 'static), and Rust lets you replace a parameter type with one of its supertypes when implementing a trait method (it makes the method strictly more general, even though it's apparently not used much in type checking).





In order to make your implementation as generic as possible, you could introduce a lifetime parameter on the impls:



use std::cmp::Ordering;

pub trait SimpleOrder {
fn key(&self) -> u32;
}

impl<'a> PartialOrd for dyn SimpleOrder + 'a {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl<'a> Ord for dyn SimpleOrder + 'a {
fn cmp(&self, other: &Self) -> Ordering {
self.key().cmp(&other.key())
}
}

impl<'a> PartialEq for dyn SimpleOrder + 'a {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}

impl<'a> Eq for dyn SimpleOrder + 'a {}





share|improve this answer





















  • 2





    That makes sense. I do believe that the error message generated by Rust should be improved though - that this is the issue isn't at all clear from the error message.

    – orlp
    48 mins ago











  • "So now we have that self has type &(dyn SimpleOrder + 'a) while other has type &(dyn SimpleOrder + 'static)." – this should be the other way round, right?

    – Chronial
    48 mins ago











  • @Chronial oops, fixed.

    – Francis Gagné
    47 mins ago






  • 3





    The thing that is surprising to me here is that calling SimpleOrder::cmp(self, other) does not check against the signature of SimpleOrder::cmp (which would succeed), but against the signature of Ord::cmp (which fails).

    – Chronial
    45 mins ago











  • @Chronial: I think this would warrant its own (separate) question; notably, I guess it would be possible to trigger the behavior without dyn Trait, just exploiting sub-typing with regular types containing references.

    – Matthieu M.
    17 mins ago
















9














Trait object types have an associated lifetime bound, but it can be omitted. A full trait object type is written dyn Trait + 'a (when behind a reference, parentheses must be added around it: &(dyn Trait + 'a)).



The problem is that when a lifetime bound is omitted, the compiler uses different rules to infer the lifetime based on the location where the trait object type is used.



First, we have:



impl PartialOrd for dyn SimpleOrder {


Here, the compiler implies + 'static. Lifetime parameters are never introduced on impl blocks (as of Rust 1.32.0).



Next, we have:



    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {


The type of other is inferred to be &(dyn SimpleOrder + 'a), where 'a is an implicit lifetime parameter introduced on partial_cmp.



    fn partial_cmp<'a>(&self, other: &(dyn SimpleOrder + 'a)) -> Option<Ordering> {


So now we have that self has type &(dyn SimpleOrder + 'static) while other has type &(dyn SimpleOrder + 'a). What's the problem?



Indeed, cmp doesn't give any error, because its implementation doesn't require that the lifetime of the two trait objects be equal. Why does partial_cmp care, though?



Because partial_cmp is calling Ord::cmp. When type checking a call to a trait method, the compiler checks against the signature from the trait. Let's review that signature:



pub trait Ord: Eq + PartialOrd<Self> {
fn cmp(&self, other: &Self) -> Ordering;


The trait requires that other be of type Self. That means that when partial_cmp calls cmp, it tries to pass a &(dyn SimpleOrder + 'a) to a parameter that expects a &(dyn SimpleOrder + 'static), because that's what Self is. This conversion is not valid, so the compiler gives an error.



So then, why is it valid to set the type of other to &(dyn SimpleOrder + 'a) when implementing Ord? Because &(dyn SimpleOrder + 'a) is a supertype of &(dyn SimpleOrder + 'static), and Rust lets you replace a parameter type with one of its supertypes when implementing a trait method (it makes the method strictly more general, even though it's apparently not used much in type checking).





In order to make your implementation as generic as possible, you could introduce a lifetime parameter on the impls:



use std::cmp::Ordering;

pub trait SimpleOrder {
fn key(&self) -> u32;
}

impl<'a> PartialOrd for dyn SimpleOrder + 'a {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl<'a> Ord for dyn SimpleOrder + 'a {
fn cmp(&self, other: &Self) -> Ordering {
self.key().cmp(&other.key())
}
}

impl<'a> PartialEq for dyn SimpleOrder + 'a {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}

impl<'a> Eq for dyn SimpleOrder + 'a {}





share|improve this answer





















  • 2





    That makes sense. I do believe that the error message generated by Rust should be improved though - that this is the issue isn't at all clear from the error message.

    – orlp
    48 mins ago











  • "So now we have that self has type &(dyn SimpleOrder + 'a) while other has type &(dyn SimpleOrder + 'static)." – this should be the other way round, right?

    – Chronial
    48 mins ago











  • @Chronial oops, fixed.

    – Francis Gagné
    47 mins ago






  • 3





    The thing that is surprising to me here is that calling SimpleOrder::cmp(self, other) does not check against the signature of SimpleOrder::cmp (which would succeed), but against the signature of Ord::cmp (which fails).

    – Chronial
    45 mins ago











  • @Chronial: I think this would warrant its own (separate) question; notably, I guess it would be possible to trigger the behavior without dyn Trait, just exploiting sub-typing with regular types containing references.

    – Matthieu M.
    17 mins ago














9












9








9







Trait object types have an associated lifetime bound, but it can be omitted. A full trait object type is written dyn Trait + 'a (when behind a reference, parentheses must be added around it: &(dyn Trait + 'a)).



The problem is that when a lifetime bound is omitted, the compiler uses different rules to infer the lifetime based on the location where the trait object type is used.



First, we have:



impl PartialOrd for dyn SimpleOrder {


Here, the compiler implies + 'static. Lifetime parameters are never introduced on impl blocks (as of Rust 1.32.0).



Next, we have:



    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {


The type of other is inferred to be &(dyn SimpleOrder + 'a), where 'a is an implicit lifetime parameter introduced on partial_cmp.



    fn partial_cmp<'a>(&self, other: &(dyn SimpleOrder + 'a)) -> Option<Ordering> {


So now we have that self has type &(dyn SimpleOrder + 'static) while other has type &(dyn SimpleOrder + 'a). What's the problem?



Indeed, cmp doesn't give any error, because its implementation doesn't require that the lifetime of the two trait objects be equal. Why does partial_cmp care, though?



Because partial_cmp is calling Ord::cmp. When type checking a call to a trait method, the compiler checks against the signature from the trait. Let's review that signature:



pub trait Ord: Eq + PartialOrd<Self> {
fn cmp(&self, other: &Self) -> Ordering;


The trait requires that other be of type Self. That means that when partial_cmp calls cmp, it tries to pass a &(dyn SimpleOrder + 'a) to a parameter that expects a &(dyn SimpleOrder + 'static), because that's what Self is. This conversion is not valid, so the compiler gives an error.



So then, why is it valid to set the type of other to &(dyn SimpleOrder + 'a) when implementing Ord? Because &(dyn SimpleOrder + 'a) is a supertype of &(dyn SimpleOrder + 'static), and Rust lets you replace a parameter type with one of its supertypes when implementing a trait method (it makes the method strictly more general, even though it's apparently not used much in type checking).





In order to make your implementation as generic as possible, you could introduce a lifetime parameter on the impls:



use std::cmp::Ordering;

pub trait SimpleOrder {
fn key(&self) -> u32;
}

impl<'a> PartialOrd for dyn SimpleOrder + 'a {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl<'a> Ord for dyn SimpleOrder + 'a {
fn cmp(&self, other: &Self) -> Ordering {
self.key().cmp(&other.key())
}
}

impl<'a> PartialEq for dyn SimpleOrder + 'a {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}

impl<'a> Eq for dyn SimpleOrder + 'a {}





share|improve this answer















Trait object types have an associated lifetime bound, but it can be omitted. A full trait object type is written dyn Trait + 'a (when behind a reference, parentheses must be added around it: &(dyn Trait + 'a)).



The problem is that when a lifetime bound is omitted, the compiler uses different rules to infer the lifetime based on the location where the trait object type is used.



First, we have:



impl PartialOrd for dyn SimpleOrder {


Here, the compiler implies + 'static. Lifetime parameters are never introduced on impl blocks (as of Rust 1.32.0).



Next, we have:



    fn partial_cmp(&self, other: &dyn SimpleOrder) -> Option<Ordering> {


The type of other is inferred to be &(dyn SimpleOrder + 'a), where 'a is an implicit lifetime parameter introduced on partial_cmp.



    fn partial_cmp<'a>(&self, other: &(dyn SimpleOrder + 'a)) -> Option<Ordering> {


So now we have that self has type &(dyn SimpleOrder + 'static) while other has type &(dyn SimpleOrder + 'a). What's the problem?



Indeed, cmp doesn't give any error, because its implementation doesn't require that the lifetime of the two trait objects be equal. Why does partial_cmp care, though?



Because partial_cmp is calling Ord::cmp. When type checking a call to a trait method, the compiler checks against the signature from the trait. Let's review that signature:



pub trait Ord: Eq + PartialOrd<Self> {
fn cmp(&self, other: &Self) -> Ordering;


The trait requires that other be of type Self. That means that when partial_cmp calls cmp, it tries to pass a &(dyn SimpleOrder + 'a) to a parameter that expects a &(dyn SimpleOrder + 'static), because that's what Self is. This conversion is not valid, so the compiler gives an error.



So then, why is it valid to set the type of other to &(dyn SimpleOrder + 'a) when implementing Ord? Because &(dyn SimpleOrder + 'a) is a supertype of &(dyn SimpleOrder + 'static), and Rust lets you replace a parameter type with one of its supertypes when implementing a trait method (it makes the method strictly more general, even though it's apparently not used much in type checking).





In order to make your implementation as generic as possible, you could introduce a lifetime parameter on the impls:



use std::cmp::Ordering;

pub trait SimpleOrder {
fn key(&self) -> u32;
}

impl<'a> PartialOrd for dyn SimpleOrder + 'a {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl<'a> Ord for dyn SimpleOrder + 'a {
fn cmp(&self, other: &Self) -> Ordering {
self.key().cmp(&other.key())
}
}

impl<'a> PartialEq for dyn SimpleOrder + 'a {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}

impl<'a> Eq for dyn SimpleOrder + 'a {}






share|improve this answer














share|improve this answer



share|improve this answer








edited 47 mins ago

























answered 53 mins ago









Francis GagnéFrancis Gagné

32.1k26679




32.1k26679








  • 2





    That makes sense. I do believe that the error message generated by Rust should be improved though - that this is the issue isn't at all clear from the error message.

    – orlp
    48 mins ago











  • "So now we have that self has type &(dyn SimpleOrder + 'a) while other has type &(dyn SimpleOrder + 'static)." – this should be the other way round, right?

    – Chronial
    48 mins ago











  • @Chronial oops, fixed.

    – Francis Gagné
    47 mins ago






  • 3





    The thing that is surprising to me here is that calling SimpleOrder::cmp(self, other) does not check against the signature of SimpleOrder::cmp (which would succeed), but against the signature of Ord::cmp (which fails).

    – Chronial
    45 mins ago











  • @Chronial: I think this would warrant its own (separate) question; notably, I guess it would be possible to trigger the behavior without dyn Trait, just exploiting sub-typing with regular types containing references.

    – Matthieu M.
    17 mins ago














  • 2





    That makes sense. I do believe that the error message generated by Rust should be improved though - that this is the issue isn't at all clear from the error message.

    – orlp
    48 mins ago











  • "So now we have that self has type &(dyn SimpleOrder + 'a) while other has type &(dyn SimpleOrder + 'static)." – this should be the other way round, right?

    – Chronial
    48 mins ago











  • @Chronial oops, fixed.

    – Francis Gagné
    47 mins ago






  • 3





    The thing that is surprising to me here is that calling SimpleOrder::cmp(self, other) does not check against the signature of SimpleOrder::cmp (which would succeed), but against the signature of Ord::cmp (which fails).

    – Chronial
    45 mins ago











  • @Chronial: I think this would warrant its own (separate) question; notably, I guess it would be possible to trigger the behavior without dyn Trait, just exploiting sub-typing with regular types containing references.

    – Matthieu M.
    17 mins ago








2




2





That makes sense. I do believe that the error message generated by Rust should be improved though - that this is the issue isn't at all clear from the error message.

– orlp
48 mins ago





That makes sense. I do believe that the error message generated by Rust should be improved though - that this is the issue isn't at all clear from the error message.

– orlp
48 mins ago













"So now we have that self has type &(dyn SimpleOrder + 'a) while other has type &(dyn SimpleOrder + 'static)." – this should be the other way round, right?

– Chronial
48 mins ago





"So now we have that self has type &(dyn SimpleOrder + 'a) while other has type &(dyn SimpleOrder + 'static)." – this should be the other way round, right?

– Chronial
48 mins ago













@Chronial oops, fixed.

– Francis Gagné
47 mins ago





@Chronial oops, fixed.

– Francis Gagné
47 mins ago




3




3





The thing that is surprising to me here is that calling SimpleOrder::cmp(self, other) does not check against the signature of SimpleOrder::cmp (which would succeed), but against the signature of Ord::cmp (which fails).

– Chronial
45 mins ago





The thing that is surprising to me here is that calling SimpleOrder::cmp(self, other) does not check against the signature of SimpleOrder::cmp (which would succeed), but against the signature of Ord::cmp (which fails).

– Chronial
45 mins ago













@Chronial: I think this would warrant its own (separate) question; notably, I guess it would be possible to trigger the behavior without dyn Trait, just exploiting sub-typing with regular types containing references.

– Matthieu M.
17 mins ago





@Chronial: I think this would warrant its own (separate) question; notably, I guess it would be possible to trigger the behavior without dyn Trait, just exploiting sub-typing with regular types containing references.

– Matthieu M.
17 mins ago


















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54329200%2fmysterious-lifetime-issue-while-implementing-trait-for-dyn-object%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Callistus I

Tabula Rosettana

How to label and detect the document text images