Is this suppose to type check?

Ran into something unusual while fighting the borrow checker. This little snippet of code type-checks but in essence its a recursive call (i.e. a call to as_ref() recurs forever) that ends in a stack overflow.

A link to - Rust Playground

The funny thing is I don't intend to call as_ref for what I'm working on but I needed it to type-check. I'm not sure if this a bug.

PS: If it is a bug, I'm just glad, I finally one-up'd the type-checker


#[derive(Debug, Clone)]
pub struct State {
    state: usize,
}

impl AsRef<[u8]> for State {
    fn as_ref(&self) -> &[u8] {
        self.as_ref()
    }
}

Your code is a fancier version of:

fn oops() -> u32 {
    oops()
}

Which does type-check, but overflows the stack because the function keeps calling itself forever. In your case as_ref() calls itself forever.

Did you mean to expose the usize field as bytes? Rust is going to get as_ne_bytes for it someday. For now you will need to do some pointer casting and std::slice::from_raw_parts.

3 Likes

I actually stumbled upon this while writing some generic code (a bit lengthy so I'll summarize). My generic type needed the AsRef<[u8]> trait bound and State - in this example is one of the possible (generic) types which is required to satisfy the - AsRef<[u8]> bound

So, I just did this and it worked. I only need the trait-bound for my other possible (generic) types such as a [u8; N]

It's almost never a compiler bug. Of course a function that only calls itself typechecks: its arguments and return type are the same as… well, itself!

In general, there could be an argument in favor of detecting infinite recursion, infinite loops, etc., however:

  1. it's not possible to detect arbitrary runtime behavior, divergence included, in the general case.
  2. Infinite loops and/or infinite recursion can be intentional (e.g. for a background process that is not supposed to stop, like a web server).

So, the compiler just plainly doesn't try to solve the halting problem.

5 Likes

Suspected it but thanks for confirming. Although in my case, its a bit hilarious that we can take advantage of it to pass type-checking

I don't think it qualifies as tricking the type checker.

If you just need something to make it type-check, a better choice is perhaps the todo!() macro.

6 Likes

True but I wasn't actually looking for a way to make it type-check. Just something that I happen to come across.

However the compiler tries to detect simple cases (like op's) anyway and warns about them. This is useful when for example when you're delegating the implementation of a trait for &T to that of T but you mess up and end up calling the method for &T, leading to infinite recurison.

You can also use loop {} to "trick" the type-checker although that's the intended behavior.

1 Like

I see the word trick seems too good to let it slide, so I'm going to remove it so that it doesnt get piled on.

Yes, that’s supposed to typecheck.

A type signature on a function promises that, if the function converges, then the result will be of the specified type. It does not promise that the function actually converges - and in general that property (provably) can’t be checked.

The return value of as_ref is guaranteed to be &[u8] if as_ref converges, and that’s enough to satisfy the requirement that all return values out of as_ref must be &[u8], so the function body is valid. The function happens not to converge, but that’s allowed for any function.

One consequence of this is that expressions like panic!(...), which are guaranteed not to converge, satisfy any type.

4 Likes

Even the otherwise-unsatisfiable Never type!

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.