Function vs trait

Beginner question -
Why would someone use a trait rather than various functions embedded in and "envelope-function" that would qualify the data-type of the input before executing the proper embedded functions? It seems as though a trait is just a different way of doing the exact same thing. ?

1 Like

I don’t quite follow what you’re descriping here. Perhaps some more explanation or comparison to other languages doing something like this or code examples would help?

Note that traits in Rust are mostly a compile-time concept; ignoring the special case of “trait objects”, calling trait functions does not involve any kind of “function object” (i.e. “function pointers”) at run-time. So one answer to your question might be that traits are more efficient. Another might be that (depending on what exactly that thing is that you’re describing) traits interact nicely with static type-checking, whereas your description of “qualify the data-type of the input before executing the proper embedded functions” sounds like a dynamic typing things.

4 Likes

It seems that I don't understand well enough to ask a coherent question, but the books that I've been reading would perform some process with a function, then say "let's do it a better way" and then describe methods, and then again "let's do it another way" and go on to describe traits. The only difference that I have been able to gather is the means by which they are called. There is a "function" inside the implementation blocks of both the method and trait examples. They all show examples that are using "static" input, i.e., they assume that the data magically appeared from somewhere, so typed into the example being illustrated. None of these books has explained why I need to create method or a trait. It seems that I could do everything with functions. ?

Traits are useful in generic programming, similar to concepts of an “interface” in other languages. Their origins are (under the name “type class”) for operator overloading in functional programming, and Rust, too, uses traits (among other things) to allow user-defined operator implementations for operations such as == or < or + operators.

I can’t comment on anything I haven’t read myself, but maybe those “books you’ve been reading” presumably had the goal to explain how to work with traits (on an introductory level) and less why they’re necessary/useful. If those are publicly available resources, feel free to leave a link to help me understand what information you’re basing your question on, and in particular what kind of use-cases that traits allow might not be covered by what you’ve seen so far.

To learn more about generic programming and traits, feel free to read the respective chapter in the book “The Rust Programming Language”.

2 Likes

Yes, that is one of the books. p. 182 "A trait tells the Rust compiler about functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic can be any type that has certain behavior."

Why can one not write a function that performs the same way? E.g., listing 10-13 --- if Summary(NewsArticle) then format!(yada yada), if Summary (Tweet) then format!(a different yada yada).

I don't understand why defining a trait for this behavior is special.

See also: What is the difference between impl fn vs traits

2 Likes

One certainly can, in Rust you would probably use an enum for this:

enum Summarizeable<'a> {
    NewsArticle(&'a NewsArticle),
    Tweet(&'a Tweet),
}

fn summarize(summarizeable: Summarizeable) -> String {
    match summarizeable {
        Summarizeable::NewsArticle(news_article) => format!(
            "{}, by {} ({})",
            news_article.headline, news_article.author, news_article.location
        ),
        Summarizeable::Tweet(tweet) => format!("{}: {}", tweet.username, tweet.content),
    }
}

fn main() {
    let tweet = Tweet {
        username: "user".to_string(),
        content: "hello, world!".to_string(),
        reply: false,
        retweet: false,
    };
    summarize(Summarizeable::Tweet(&tweet));
}

But this has some key differences compared to using traits:

  1. It is not extensible. If this summarization feature is part of some external crate, I can not modify the Summarizeable type or summarize functions to add my own types there. With traits, I can simply impl Summarize for MyType, and now I can call summarize with it as well as other generic functions that accept types that implement Summarize. Sometimes being extensible is not desireable, but often it is.
  2. It's slower. Checking which variant of the enum we're dealing with isn't expensive, but it's not free either. With traits, the compiler will make it so summarize(tweet) calls Tweet's summarize function with no check needed at runtime.
  3. You need to put the argument inside the enum to call it, which is a little inconvenient.
5 Likes

The book presents e.g. these applications of traits:

  • generic functions that are able to operate on all kinds of types, provided they implement Summary, as shown starting from the heading “Traits as Parameters”. This is in line with the whole concept of generic programming, as introduced in the previous subchapter, i.e. without such a generic function, you’d need to copy-paste the code to create lots of non-generic versions of the function fn notify_tweet(item: &Tweet) { … } and fn notify_news_article(item: &NewsArticle) { … }, and so on.

  • a way to say “this type supports comparison operations”, as demonstrated in the running example of a generic largest function here

  • a way to say “this type supports comparison operations and is supported by {} format strings in println” and similar complex conditions to allow implementing generic functions/methods (here)

In the end of the day, traits are absolutely not necessary to write software. Rust is “Turing complete” without them (the same can be said about generics in general, too, I guess). A reoccurring theme in the list above is “traits are used by many standard library constructs”, so you use traits (e.g. by implementing them, or writing trait bounds) in order to interact with the standard library, not necessarily because traits are absolutely necessary in general, but because they are the way to interact with these parts of the standard library.


I don’t know about your potential background in other programming languages, but traits do solve some problems that some other languages have. Comparing to C++ templates, trait bounds allow Rust to type-check generic functions, and avoid highly-cryptic template-instantiation-time errors. Comparing to many less-strictly-typed languages, traits allow you to specify whether or not your custom types actually support == operators, or hashing, whereas – say – in Java, simply all types are always (kinda) assumed to support such operations, and putting certain kinds of values in a HashMap will simply misbehave, but not give compilation errors; and equality-testing with == might be questionable because it always checks for some kind of object-identity, even for custom value-types.

3 Likes

Thanks. I have no background in programming. I'm a structural engineer -- modulus of elasticity, time-history and such.

2 Likes