What's the Difference Between Implicit vs. Explicit Programming?

Written by: Daniel P. Clark

The terms implicit and explicit take some getting used to when you first hear them. When you hear them in terms of programming what exactly does that mean to you? Is one way better than another? Here we'll go over each of these ideas and provide examples with some design pattern benefits that may come from them.

The Terms

In programming, implicit is often used to refer to something that's done for you by other code behind the scenes. Explicit is the manual approach to accomplishing the change you wish to have by writing out the instructions to be done explicitly. In the smaller picture, implicit and explicit are often terms used for casting types in to the type you would have it to be. In the bigger picture, you may be talking about convention over configuration where convention is what is implicitly done for you by a code base or framework and configuration is simply explicit settings.

There are also differences of appropriateness or benefit for either use depending on the programming language in question and whether the language is statically typed or dynamically typed. It also depends on whether things can be inferred from a run time or during compilation. Because of these factors, making a claim that one methodology is better than another can only possibly be true within narrow contexts as you have to take into consideration the design for programming languages and programs in question.

An example of implicit and explicit type casting in C is as follows:

int implicit;
implicit = 4.5;
int explicit;
explicit = (int)4.5; 

Here the variable names implicit and explicit were defined to be of type int. Once given a value 4.5 the implicit version has the compiler convert what would normally be a float or double type to an integer whereas the explicit version has explicitly cast it to an integer with the use of (int) being what casts the type.

Statically-typed languages

In statically-typed languages such as Rust a vast majority of value creation and assignment will have explicit type annotation requirements with an exception for wherever the compiler can infer the type. The following is an example showing explicit and implicit types in Rust.

fn add_one(input: u8) -> u8 {
    input + 1
}
let four = add_one(3);

Here the method add_one is explicit about the input type and output type. The number 1 being added here is implicitly made an u8 number at compilation time as the context helps infer the type and addition for u8 is only implemented to work with u8 numbers. The last line is implicitly typed to be an u8 since the method itself defines the type that will be returned.

What can be inferred in Rust is pretty impressive with the use of generics. But use of inference is limited to things that can be known at compile time. If it cannot be known at compile time then you must explicitly define the type at any point of assignment. Take the following method definition:

use std::ops::Add;
fn add_both<T: Add>(a: T, b: T) -> T::Output {
  a + b
}

Here T can be any type that implements the trait Add. The T::Output type is defined when the Add trait is defined for your specific type and is generally the same type as T itself in this case. Now if we provide two numbers as parameters the compiler will infer the type to it.

let x      = add_both(3   , 4   ); // implicit type
let y: u8  = add_both(3u8 , 4u8 ); // explicit type
let z: u32 = add_both(3u32, 4u32); // explicit type

When I run the above code x is inferred to be the type i32. The y and z examples require the input parameter types to be known when provided to the function as the inference for T::Output isn't necessarily the same as what T may be inferred to as. It would default to i32 and then assigning an i32 as a u8 or u32 is simply wrong.

Dynamically-typed languages

Now with dynamically-typed languages, you have to worry less about types, per se, and more about implicit or explicit objects or behavior. Modeling code around objects which have the same behavior is what is known as duck typing which is a higher domain of thinking in object-oriented programming where handling those objects is implicit. Whereas modeling the code around specific object classes is much like using explicit or implicit typing. But when you accept any kind of object as input, then the code in that section must either handle different objects explicitly or hand that responsibility off elsewhere.

For clarification, when you are writing code to handle different kinds of things, then you are writing explicit code. However when this same code has already been written and you are reusing it with a simple method call, then the behavior in this new context is then implicit. Implicit being things done as though automatically and handled outside of your current scope.

In Ruby, most types have a design for explicit or implicit conversion. The idea is the implicit conversion methods are meant to be used in implicit contexts and the explicit conversions are meant for developers to write inline in much more contexts. Let me demonstrate this by example.

# explicit
"4".to_i + "5".to_i
# => 9
# implicit
class Seven
  def to_int
    7
  end
end
Array.new(Seven.new)
# => [nil, nil, nil, nil, nil, nil, nil]

Here the explicit example makes it very obvious to the reader that we are converting String objects to an Integer object and performing addition between the two. And the implicit example isn't so obvious to the reader as the Array.new method implicitly calls the to_int method on whatever parameter it is given. The Integer class has the method to_int defined on each instance of it that simply return self. If you write 42.to_int you simply get back 42. This use of using implicit conversion as an input guard for method calls is a great design for type safety by design. Here's what happens if you give it the wrong kind of object as input which doesn't define to_int.

Array.new("32")
# TypeError (no implicit conversion of String into Integer)

Not only does it fail but it gives us a helpful message that an implicit conversion was attempted and tells the class of object given and the class of object it expects. Implicit conversion methods in Ruby is a way of saying this object really is what you're expecting. And explicit conversion is simply making the conversion to the type expected.

Ruby has implicit and explicit options for many of its core objects.

Array#to_a     // explicit
Array#to_ary   // implicit
Hash#to_h      // explicit
Hash#to_hash   // implicit
Kernel#to_s    // explicit
String#to_i    // explicit
String#to_str  // implicit
Integer#to_i   // explicit
Integer#to_s   // explicit
Integer#to_int // implicit

Ruby also has a few classes with class methods that will either do a implicit conversion or return nil and that method name is try_convert.

Array.try_convert  // calls `to_ary` on the parameter or returns `nil`
Hash.try_convert   // calls `to_hash` on the parameter or returns `nil`
String.try_convert // class `to_str` on the parameter or returns `nil`

We can follow Ruby's example with Array.new and have a good guard for the kind of input parameters we're given by designing implicit conversions for our own custom types. For this example, since to_f is an explicit conversion to a Float number in Ruby we'll use as_ as a prefix instead of to_. Here's a basic example of implicitness as a pattern for safety.

class Foo
  def as_f
    self
  end
  def as_foo
    self
  end
end
class Bar
  def initialize(foo)
    begin
      @foo = foo.as_foo
    rescue NoMethodError
      raise TypeError, "no implicit conversion of #{foo.class} into Foo"
    end
  end
end
Bar.new(4)
# Traceback (most recent call last):
# ...
# TypeError (no implicit conversion of Integer into Foo)
Bar.new(Foo.new)
# => #<bar:0x00005600711d10d8 @foo=#<Foo:0x00005600711d1100>></bar:0x00005600711d10d8>

Now this will help other developers who use the Bar class to not pass it as an incompatible parameter. It follows a convention within the Ruby language and should be much more easy for developers to understand with more helpful errors. When you have other objects that you would like to convert into a Foo object then you can define an as_f method on it for an explicit conversion and the developer who uses that new object will be explicit in its use Bar.new(Baz.new.as_f). This ensures the code will work in so much as Bar and Foo are already working.

Summary

The act of implicit and explicit coding, and implicit and explicit code, is defined by context of execution of additional behavior or type setting/casting. Specifically, implicit or explicit methods are defined by the contexts in which they are meant to be used. Implicit code can be a very nice experience if things are well named as it keeps things simple.

However, implicit code, code doing things behind the scenes for you, can also be a difficult issue to solve when done wrong. Explicit code makes the code clear when you look at it as the specifics of what's being done is already laid out before you and you don't have to track down issues elsewhere, but generally means a lot more work writing it. On its own it can become overwhelming so finding a proper balance between the two is often the best solution. Be explicit when you must, and implicit when the design and naming concepts are easy to understand. It will make for a more convenient development experience.

Additional resources

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.