-
-
Notifications
You must be signed in to change notification settings - Fork 163
Feature Detection Is Better than Version Detection
Summary: Version Detection like checking $BASH_VERSION == "4.4"
or User-Agent == "Mozilla"
is bad for portability.
Instead, it's better to test if the feature exists:
- with
eval()
, in JavaScript, shell, Python, etc. - by writing a
./configure
script, for C, C++, etc.- (preferably without autoconf, although autoconf does follow the philosohpy of feature detection)
-
Comment on Hacker News Thread: Bash Completion in JSON Fields
- An unnecessary
$BASH_VERSION
check --eval 'complete ...'
would suffice.
- An unnecessary
- All browsers pretend they're Mozilla because web servers detect it
- LLVM Has to Pretend It's GCC to compile code in the wild
- Despite its flaws, GNU autoconf (
./configure
) works because it uses feature detection. If it had used version detection, it would have collapsed from complexity a long time ago, and been a lot less reliable.
NOTE: OSH won't pretend it's bash! It is largely compatible with bash, but not identical.
In shell (or JavaScript), an easy and effective way is to use eval
. (This is perhaps the best reason to use eval
!)
For example, you can test if a shell has declare
with something like
if eval 'declare myvar'; then
have_declare=yes
else
have_declare=''
fi
# usage:
if test -n "$have_declare"; then
...
fi
In JavaScript, Python, or shell, you might do
eval('await foo()') # newer JS feature
to probe for a language feature.
In a ./configure
script, you might try to compile a file like:
int main() {
co_yield "foo"; // newer C++ feature
}
This seems like evidence that Rust should support feature detection, not just version detection.
We have some gripes with [Rust].
Chief among them is how Rust handles portability. While it offers many abstractions over systems, allowing you to target a variety of systems with the same code, when it comes to adapting your code to systems at a lower-level, it’s all based on enumerating systems by hand, using checks like
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
.
This is an imperfect solution, allowing you to miss systems and ignoring version differences entirely. From what we can tell, if FreeBSD 12 gains a function that we want to use, libc would add it, but calling it would then fail on FreeBSD 11 without a good way to check, at the moment.
But listing targets in our code is also fundamentally duplicating work that the libc crate (in our case) has already done.