
Rust's Type System Is Turing-Complete: Type-Level Programming in Rust - sleffy
https://sdleffler.github.io/RustTypeSystemTuringComplete/
======
tatterdemalion
Hey I'm the person who raised the question (and I work on Rust, mainly on the
trait system discussed in this post).

I agree with the author that while this is interesting, for users it doesn't
matter much. In particular, the folks (with respect) pontificating about
whether this is a good or bad thing are quite exagerating its consequences. In
practice, you can neither conveniently use this property for metaprogramming,
nor will you ever get a nonterminal typechecking (not only is there a
recursion limit, it is extremely unlikely you will write the kinds of impls
that hit that limit except in extreme typelevel programming like documented
here).

The most interesting thing is that the two examples we have now of turing
completeness both use only features available in Haskell without making
typechecking undecidable. The reason our system is Turing complete is that we
allow certain impls that Haskell wouldn't, which are in practice very useful.

For example, we have an impl of "ToString for T where T: Display" (in
Haskellese "instance Display a => ToString a"); that is, anything that can be
pretty printed also has a to_string method. This would be disallowed without
the -XUndecidableInstances flag in Haskell. However, if your type can't be
pretty printed, you can separately add a ToString impl for it (even with
undecidable instances, Haskell wouldn't consider this coherent because Haskell
coherence ignores constraints).

If you're interested, the exact sorts of impls we allow that Haskell wouldn't
are covered by what's called the "Paterson condition" in Haskell docs.

~~~
hornetblack
I have managed to hit the recursion limit in rust before:
[https://is.gd/7IH5gE](https://is.gd/7IH5gE)

What happens in rust closures have a unique type[1]. So the recursive call in
cps_factorial, instantiates a new cps_factorial which contains a new unique
closure type which instantiates a new cps_factorial...

[1] This is done so that closure calls can be determined statically reducing
run-time overhead and helps optimisation.

~~~
mickronome
I'm quite of my depth here, and while I can sort of understand the recursive
construction of new types, it seems as if the type checker should be able to
almost trivialy 'shortcut' the recursion unless the return type is
unconstrained in some sense that it can be a recursive type with some type
arguments.

Most likely there is something obvious I'm missing as I don't know a lot about
rust, but unless the compiler isn't doing what is essentially a type level
equivalent of tail call elimination, which it almost certainly does, finding
the type of that function should be trivial unless the return type can become
recursive with unbounded depth.

~~~
tatterdemalion
This example code must be monomorphized at compile time, meaning that each
closure will have a different representation. "Tail call optimization at the
type level" is not possible with this compilation model.

(The problem is solved by using a trait object to dynamically dispatch the
inner call. [https://play.rust-
lang.org/?gist=0d3c0a5f1291eb34ba61bd39449...](https://play.rust-
lang.org/?gist=0d3c0a5f1291eb34ba61bd39449cb5eb&version=stable&backtrace=0))

~~~
hornetblack
From memory I solved it using more CPS and currying. Well manually currying
with functions like curry1, curry2, etc...

Although I'm pretty sure you can overload the Fn* traits to implement currying
in Rust. (Fn traits are unstable). Which is neat.

    
    
        // Something like this:
        impl<F, A, B, R> (FnOnce(A) -> Curry<F, A, B>)  for  F
            where F: FnOnce(A, B) -> R,
        { ... }
    
        // and
        impl<F, A, B, R> (FnOnce(B) -> R) for Curry<F, A, B>
            where F: FnOnce(A, B) -> R
    
    

I think I got it working... but you would need some work for more arguments.
(And it worked different on different versions of rust-nightly :/)

------
dkarapetyan
New rule. Any sufficiently advanced type system is an ad-hoc re-implementation
of Prolog. Once you have unification and backtracking you basically have
Prolog and can implement whatever Turing machine you want with the type
system.

~~~
tatterdemalion
In the near future, Rust's trait system literally will be a logic language
similar to Prolog that runs at compile time. We walk the AST and generate
statements to prove. This is expected to be easier to reason about, to be
easier to extend, and to have shorter compile times - for essentially the
reason you cite. Its even given us some (as yet unexplored) ideas for new
extensions.

~~~
deegles
Serious question... should I spend time in 2017 on Prolog? It seems like
another case of "everything old is new again"...

~~~
qznc
Yes, for the concepts. Just like it helps to know relational databases, even
if you always use an ORM in practice.

It is debatable if Datalog is enough or if cuts et al are worthwhile.

~~~
fauigerzigerk
Not sure if ORMs are a good analogy here. ORMs are a leaky and misleading
abstraction on top of an actual RDBMS.

~~~
charlieflowers
The GP seems to be thinking in the opposite direction. His analogy is that
knowing an ORM but not SQL is like not knowing Prolog, and that adding
knowledge of SQL to your ORM knowledge is like learning Prolog.

So the argument is that it is a similar eye-opening transition from ignorance
to awareness.

------
dvt
This is cool but generally a bad idea. It introduces weird edge-case scenarios
where you have to 'artificially' halt the compiler, as type-resolution (in
these kinds of systems) is undecidable. Rust halts type-resolution by limiting
recursion, for example. This is sensible, as @tatterdemalion argues, but it
feels like a necessary fix created by an unnecessary problem.

This code, for example, will never stop compiling in gcc 4.4.1[0]:

    
    
      template<int I>
      struct Infinite
      {
        enum { value = (I & 0x1)? Infinite<I+1>::value : Infinite<I-1>::value };
      };
    
      int main ()
      {
        int i = Infinite<1>::value;
      }
    
    

[0] [http://stackoverflow.com/questions/6079603/infinite-
compilat...](http://stackoverflow.com/questions/6079603/infinite-compilation-
with-templates)

~~~
kentonv
So don't do that.

As programmers, we can indeed write infinite loops. Why is it "a bad idea" if
the loop happens at compile time rather than runtime? Why should we reduce the
expressiveness of our language to avoid this possibility?

~~~
dvt
Because it's not necessary. If a Turing-complete programming language can't be
expressive enough without a Turing-complete metalanguage, then it's probably
not designed very well.

What next? Will we start seeing meta-metalanguages because, hey, it makes
compilation less error-prone (but adds something we'll call pre-compilation)?

~~~
clock_tower
I think C/C++ #ifdefs could qualify as a meta-meta-language. I've certainly
found that there are situations where static analysis can't comprehend C++
without pre-compilation...

~~~
dvt
Yes, I think you're right, actually. I doubt the C/C++ pre-processor is
Turing-complete, though.

~~~
jepler
"yes-ish" it is turing complete
[http://stackoverflow.com/questions/3136686/is-
the-c99-prepro...](http://stackoverflow.com/questions/3136686/is-
the-c99-preprocessor-turing-complete) (read the second answer, not the
accepted answer)

~~~
dvt
Wow, TIL. Good find!

------
badcede
I wonder how many HN users see that headline and think "cool" vs. how many see
it and think "uh-oh".

~~~
curun1r
I feel like I've been digging a metaphorical hole for the entirety of the time
that I've learned about programming. Often times, I'll look up at the surface
where I started and feel pretty impressed with myself at the depth and the
breadth of my hole. Then someone will pop his/her head up from the bottom of
my hole to show me a nugget he/she has unearthed from miles below me. And
while it's undoubtedly cool and interesting, it makes me realize just how much
farther I can keep digging. It's both inspiring and ego-crushing.

~~~
bluejekyll
Ditch the ego, focus on the inspiration; you'll be happier.

------
tomjakubowski
> Rust has a feature called traits. Traits are a way of doing compile-time
> static dispatch, and can also be used to do runtime dispatch (although in
> practice that’s rarely useful.)

Hmm, I dunno about that. If you need to keep a collection of callbacks, that
would most certainly be a collection of Fn/FnMut trait objects, that is this:

    
    
        struct Thing {
            callbacks: Vec<Box<FnMut(i32) -> i32>>
        }
    

and not this:

    
    
        struct Thing<F> where F: FnMut(i32) -> i32 {
            callbacks: Vec<F>
        }
    

The same applies for any use of traits where the exact impl to be used is not
known until runtime, like choosing a behavior from some set of behaviors in a
configuration file.

~~~
leshow
How does that contradict what the post says? A trait object (the boxed fn in
your example) is used for dynamic dispatch.

OP says "can also be used". It sure looks like the Fn trait is being used for
dynamic dispatch in that example.

~~~
khuey
He's objecting to the "in practice rarely useful" bit.

------
kentonv
Good.

Metaprogramming is programming -- it's scripting the compiler, to produce code
that is faster (at runtime) or catches more errors at compile time. But for
some reason in C++, metaprogramming is written in a completely different
language from everything else -- a functional language, but not a very good
one. As a result, code that relies heavily on metaprogramming ends up
inscrutable to most programmers.

Instead, we should embrace metaprogramming and allow it to be performed in a
language that is similar to other code, so that we can understand it. Give the
compiler an actual API and let me write code that calls that API at compile
time.

(I don't know enough about Rust to know how well they've accomplished this.)

~~~
Manishearth
Generics in Rust are supposed to be used as generics, not really for
metaprogramming like this (but you can, but it won't be pleasant).

But Rust has a hygenic higher-level macro system that helps.

On top of that, Rust is getting procedural macros. Right now this is limited
to `#[derive()]` macros, but we want it to cover all cases. This means that
you can write a macro that runs arbitrary rust code at compile time.

(While it currently is limited to `#[derive()]` macros you _can_ abuse the API
to make it work for inline bang-macros as well)

------
lsiebert
On the one hand this article is extremely interesting to me. On the other
hand, I don't feel like see a lot of articles from the trenches talking about
how to do something in production for Rust, which gives the impression
(possibly false) that not many people are using Rust in production.

~~~
leshow
Are you just looking on HN? There's lots of really cool uses of Rust kicking
around. I guess it depends what you mean by production. I mean obviously
there's Rust itself, Servo, Cargo, /r/rust usually has some interesting posts.

There's also projects like ripgrep, alacritty, redoxOS, just off the top of my
head. Dropbox and npm also come to mind.

You can also check out [https://www.rust-lang.org/en-
US/friends.html](https://www.rust-lang.org/en-US/friends.html) for a big list
of companies that use Rust.

------
bsaul
Don't know enough rust to follow the article in depth, but isn't using macro a
bit cheating ? Is the type system turing complete even without macros ?

~~~
steveklabnik
Macros expand to the code you could have written yourself. It just makes it a
bit shorter and easier to write.

------
logingone
According to
[http://progopedia.com/dialect/smallfuck/](http://progopedia.com/dialect/smallfuck/)
re. Smallfuck: "The memory tape is limited, so the language is not Turing-
complete."

Edit: ah, further down in the article: "Smallfuck is a minimalist programming
language which is known to be Turing-complete when memory restrictions are
lifted."

------
LeoNatan25
> (N.B. The word “fuck” appears multiple times in this post. I recommend that
> the reader temporarily not consider “fuck” as profanity, as it isn’t used
> that way here.)

How sad is it that a serious article for grownups has to put a disclaimer
about a fucking word so that some flakes don't get offended? It's truly a sad
state of affairs.

~~~
kibwen
How sad is it that grownups jump to conclusions and construct strawmen in
order to complain about imaginary complaining? When you write about Brainfuck
on your personal blog it's entirely reasonable to put a disclaimer up top for
the benefit of laymen (including hiring managers) who are googling your name.

~~~
coldtea
> _How sad is it that grownups jump to conclusions and construct strawmen in
> order to complain about imaginary complaining?_

It would only be imaginary/strawmen if the author (and us all) hadn't already
seen the same complaining in hundreds of articles... As it is, it's merely
pre-emptive...

~~~
kibwen
Can you please link an example of a complaining comment in any of those
hundreds of articles?

~~~
coldtea
I've seen tons of those over the decade or so on HN. But just off of the top
of my he^H^H a quick google search:

" _I 'm all for cursing in private conversation with friends and such when
it's appropriate - but why do people continue to think it's acceptable in
public examples of work? Will this go on someone's resume like this? What will
an employer think?_"

[https://news.ycombinator.com/item?id=12460640](https://news.ycombinator.com/item?id=12460640)

" _Reading through the comments I am glad I am not the only one who finds over
the top profanity upsetting. Yes , the author is free to make his /her point
any way they want to. and No, not everyone who thinks that this profanity is
undue is a prude. and I dont think it is fair to make a culture or age
characterization based on a user's response. Extreme or sometimes, any
profanity changes the tone of the article. that alone is a good reason to
avoid over the top proclamations. IMO the author comes across as loud and
noisy , and not strong and forceful. Just like a stand up comedian, who says
'fuck' for every joke._"

" _potty mouth._ "

(and numerous other comments focusing on the use of "fuck" in the post)

[https://news.ycombinator.com/item?id=5694173](https://news.ycombinator.com/item?id=5694173)

" _Was it really necessary to drop an F bomb to ask this question?_ "

[https://news.ycombinator.com/item?id=11122711](https://news.ycombinator.com/item?id=11122711)

------
mybrid
A practical definition of Turing complete is any language that can exercise
the full capabilities of the computer.

Given any two languages can do so then they are in fact, interchangeable.

