What makes the Rust programming language so popular?
C and C++ have always been the golden standard when it comes to speed, but they also came with a price: managing memory (i.e. allocating and deallocating memory) is a nightmare (in general, and in multithreaded applications in particular) for developers.
Quoting Wikipedia:
Rust is a multi-paradigm, high-level, general-purpose programming language designed for performance and safety, especially safe concurrency. Rust is syntactically similar to C++, but can guarantee memory safety by using a borrow checker to validate references. Rust achieves memory safety without garbage collection, and reference counting is optional.
Nuff said. If you are reading this, you probably already know why Rust has gained so much momentum in the last few years. Rust’s strong points are an efficient memory model and a modern build toolchain (Cargo). While its learning curve is steep, it may be the right tool for a variety of tasks, such as console (CLI) and DevOps tools, embedded systems, cryptocurrencies, IOT and, effective September 2021, Device Detection with WURFL.
Starting with version 1.12.2.0, WURFL InFuze introduces a new Rust module that you can use in your applications to look up the capabilities of devices such as smartphones, tablets and desktop web browsers, as well as the long tail of HTTP clients from Smart TVs and up to the wristwatches that some use to surf the web.
This blog post illustrates that specific use-case. We will guide you through building and compiling a command-line application that reads a list of user agent strings from the standard input and writes the device name and some of its properties (WURFL capabilities) to the standard output in TSV format. We provide the code with an open-source license, but of course you will need to acquire a license for the WURFL Module from ScientiaMobile in order to build and run the demo app.
Prerequisites
In order to build the application, you’ll need:
- The Rust tool chain (you can find instructions to download and install it here)
- WURFL InFuze
- WURFL InFuze Rust module
This author was able to install Rust on the Windows Subsystem for Linux in a matter of seconds with:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Build the Application
Clone the WURFL Rust example repository:
$ git clone https://github.com/WURFL/infuze-rust-example
then
$ cd infuze-rust/infuze-rust-examples
Open the Cargo.toml
file and replace the actual path of your WURFL InFuze Rust module installation in:
wurfl = { version = "0.9.0", path = "/path-to/rust-wurfl" }.
Build the application ($ cargo build
). The executable will be created under the directory target/debug
.
Execute the application
The application expects user-agent strings to be fed to it through the standard input. The infuze-rust-examples
folder contains a file (100_ua.txt
) with a list of 100 user-agent strings provided for your convenience. You can launch the application and pipe those 100 UA strings into our Rust program:
$ cat 100_ua.txt | target/debug/infuze-rust-examples > outfile.tsv
This will work fine in many Linux distributions, as it assumes a default location of the wurfl.xml/.zip
file (/usr/share/wurfl/wurfl.zip
). If you execute the application on Windows or Mac OS or other *nix distributions, or if the wurfl.xml
file is in a different place, you can specify the location of the WURFL file with the `-w` option.
$ cat 100_ua.txt | target/debug/infuze-rust-examples -w /path/to/wurfl.zip > outfile.tsv
Look at the Code
First lines are boilerplate code (imports, argument parsing, file path validation, etc.) followed by the instructions that create the WURFL engine:
use std::{env, io}; use std::process::exit; use wurfl::{Wurfl, WurflCacheProvider}; use std::path::Path; use std::io::{BufRead, Write}; : let wurfl_res = Wurfl::new(wurfl_path.to_str().unwrap(), None, None, WurflCacheProvider::LRU, Some("100000")); let engine = match wurfl_res { Ok(engine) => engine, Err(error) => panic!("Problem initializing wurfl: {:?}", error), };
Each line
of the input contains a user agent string, i.e. the key parameter to the lookup_useragent
API call. This returns an instance of the Result<Device, WurflError>
We need to check for a possible error condition before calling unwrap()
and get the actual device instance.
We need an instance of the WURFL engine to access all API features. The first parameter is the path to the WURFL file. The two None parameters are used to define an array of WURFL patches and a WURFL capability filter, i.e. two advanced features you probably don’t care about (at least, not yet).
The last parameter configures a LRU cache of 100000 elements. In-memory caching of device lookups enables the (configurable) trade-off between speed and memory. Most users will be fine with the default.
After grabbing the standard input and output handles ( io::stdin()
and io::stdout
), we are finally ready to perform device detection on input data:
for line in stdin.lock().lines() { let device_res = engine.lookup_useragent(line.unwrap().as_str()); if device_res.is_err() { // skip some unlikely errors continue; } let device = device_res.unwrap(); let device_name = device.get_virtual_capability("complete_device_name"); let is_tablet = device.get_virtual_capability("advertised_device_os"); let form_factor = device.get_virtual_capability("form_factor"); let _ = io::stdout().write_all(device_name.unwrap().as_bytes()); let _ = io::stdout().write_all("\t".as_bytes()); let _ = io::stdout().write_all(is_tablet.unwrap().as_bytes()); let _ = io::stdout().write_all("\t".as_bytes()); let _ = io::stdout().write_all(form_factor.unwrap().as_bytes()); let _ = io::stdout().write_all("\n".as_bytes()); } }
Each line
of the input contains a user agent string, i.e. the key parameter to the lookup_useragent
API call. This returns an instance of the Result<Device, WurflError>
We need to check for a possible error condition before calling unwrap()
and get the actual device instance.
Once we have our device
, we call get_virtual_capability
for “complete_device_name
”, “advertised_device_os
” and “form_factor
”.
Virtual capabilities return properties whose value is based on the evaluation of other static capabilities or further inspection of parameters found in the HTTP request. For more information about virtual and static capabilities, you may want to refer to this page.
Note: for the purposes of this blog post, we are ignoring the return of the write_all
call that might be relevant in a real application.
Assuming there are no errors, here’s what output from the demo app looks like.
Device name OS Form factor Samsung SM-T110 (Galaxy Tab 3 Lite) Android Tablet Opera Mini 4 Feature Phone itel fp6531 Feature Phone Xiaomi Mi A1 Android Smartphone :
We are golden. If Rust is your poison, you can now start thinking of how you can augment your products and services with device intelligence.
Conclusions
Arguably, Rust is one of the most exciting new kids on the block when it comes to programming languages. Companies approached us at the beginning of 2021 and asked if we supported Rust. We didn’t at the time, but we listened!
Rust has occupied a sweet spot that wasn’t quite covered by other programming languages old and new. But, as programmers know, being good at something is only part of the story when it comes to adopting a new language, the other part is access to a rich module library to cover all use cases. Rust missed a device detection module. ScientiaMobile got you covered: with WURFL InFuze for Rust, accessing device properties is now just as easy as with any other language that we support (all major ones!).