Problems templetizing an "easy" function

Here is a fixed version using Borrow.
Playground link
The return type in any case was identical to the first argument, but an AsRef. The test code you gave shadowed the first function with a variable named first which threw things off.

In any case, the ability to write a useless function with an arbitrary type constraint isn't particularly helpful.

2 Likes

An API which guarantees the function

  • Takes two arguments
  • Always of the same type (not ignoring lifetimes)
  • And returns the first

Is only possible with redirection. I'll return to this in a moment; it's an aside to your experiment.

Your first_by_ref meets the requirements when lifetimes are abstracted away, but only works for references. There are many other types with lifetimes; you'd need more functions for any others you wanted to use (simplest example: &'_ mut T). So as you say, it's not possible with one generic function.

But I don't see how adding first_by_val expresses anything more with regards to your requirements.

It also fails the first and third requirements; you can change it to return the second argument and it still compiles. This is true even when the resolved type has no lifetimes.

You get an error because the lifetimes are not independent; they are forced to be the same, i.e. it fails requirement one due to lifetimes influencing each other. Exactly the same as my altered first_by_ref:

The error here is the the same as with your one-type-parameter first_by_val ... because my shared lifetime first_by_ref is exactly the same, restricted to reference types.


Let me return to that comment about satisfying these compiler-enforce API goals (not ignoring lifetimes):

  • Takes two arguments
  • Always of the same type
  • And returns the first

This satisfies these goals:

pub trait Id {
    type Myself: ?Sized;
}

impl<T: ?Sized> Id for T {
    type Myself = T;
}

pub fn return_first<First, Second>(first: First, _second: Second) -> First
where
    Second: Id<Myself=First>,
{
    first
}

The blanket implementation of Id means that

  • Second: Id is always true, so the where clause is always met
  • First = Second due to the body of the implementation, that is,
    • Second = Myself in Second: Id<Myself=First>

However, the body of the function can only rely on the explicit where-clause; recognizing the equality of parameters requires knowledge that isn't part of the where-clause.

So this one satisfies your requirements when the resolved type has no lifetimes. (But when there are lifetimes, those lifetimes are still forced to be equal, so this is mostly just a tangent.)

Side note to the side note:

A slight addition to the trait would make it possible to return the second parameter again:

 pub trait Id {
     type Myself: ?Sized;
+    fn into_myself(self) -> Self::Myself where Self: Sized, Self::Myself: Sized;
 }

 impl<T: ?Sized> Id for T {
     type Myself = T;
+    fn into_myself(self) -> Self::Myself where Self: Sized {
+        self
+    }
 }
pub fn return_first<First, Second>(_first: First, second: Second) -> First
where
    Second: Id<Myself=First>,
{
    second.into_myself()
}

:heavy_check_mark:

You are right, but the definition of the function was fixed. I always concentrated on the calling side.

Exactly: I get your point: It is useless to define such a generic function without knowing which traits it implmentents. However this was not intended to be covered by this topic.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.