R Internal Functions: .Internal(), .Call(), .External(), Low Level
Many R functions are thin wrappers around compiled C code. .Primitive() and .Internal() call built-in C routines inside R itself, while .Call() and .External() call C code in add-on packages. Understanding this layer explains why some functions are fast and why you can't always see their source code.
.Primitive(), The Fastest R Functions
Primitive functions are implemented directly in C with no R-level wrapper. They're the fastest operations in R:
RInspect primitive functions with typeof
# These are all primitive functionscat("typeof(sum):", typeof(sum), "\n")cat("typeof(`+`):", typeof(`+`), "\n")cat("typeof(`[`):", typeof(`[`), "\n")cat("typeof(c):", typeof(c), "\n")# Printing a primitive shows its C entry pointprint(sum)print(`+`)
RCount primitive functions in base
# You can identify primitives by their typebase_fns <-ls("package:base")primitives <-Filter(function(fn) is.primitive(get(fn, "package:base")), base_fns)cat("Total base functions:", length(base_fns), "\n")cat("Primitive functions:", length(primitives), "\n")cat("\nSample primitives:", paste(head(primitives, 10), collapse =", "), "\n")
.Internal(), Built-in R Functions
.Internal() calls C code that's compiled into R itself, but through a regular R function wrapper:
RInspect grep body for .Internal
# Many base functions use .Internal internally# For example, paste() is an R function that calls .Internal(paste(...))# You can see this by looking at the sourcecat("body of grep:\n")print(body(grep))
RClassify primitive versus Internal functions
# The difference: .Primitive skips R overhead, .Internal goes through R dispatchcat("\nsum is primitive (no R wrapper):\n")print(sum)cat("\ngamma is a regular function using .Internal:\n")# We can see the structure of functions that call .Internalis_internal <-function(fn_name) { fn <-get(fn_name, "package:base")if (is.primitive(fn)) return("primitive") b <-body(fn)if (is.null(b)) return("primitive")if (any(grepl("\\.Internal", deparse(b)))) return(".Internal")"R-level"}samples <-c("sum", "paste", "grep", "mean", "which", "length")for (fn in samples) {cat(sprintf(" %s: %s\n", fn, is_internal(fn)))}
.Call() and .External(), Package C Code
.Call() is the modern interface for R packages to call their own compiled C/C++ code:
RExplain .Call package interface
# Many CRAN packages use .Call for performance-critical code# For example, stringr/stringi use C++ for string operations# You can see .Call in package source code:# function(x) {# .Call("C_my_fast_function", x, PACKAGE = "mypackage")# }# Demonstrate: some base R functions also use .Callcat("Functions in base that use compiled code are common.\n")cat("The pattern is: R function validates input, then calls C for speed.\n\n")# Example: nchar uses C internally for speedx <-rep("hello world", 100000)t <-system.time(result <-nchar(x))cat("nchar on 100k strings:", t["elapsed"], "sec\n")cat("Result sample:", result[1], "characters\n")
Summary of Interfaces
Interface
Where the C code lives
Who uses it
Speed
.Primitive()
R core (no R wrapper)
Base R only
Fastest
.Internal()
R core (R wrapper)
Base R only
Very fast
.Call()
Package shared library
Any package
Fast
.External()
Package shared library
Older packages
Fast
.C()
Package shared library
Legacy interface
Fast (limited types)
RClassify how functions hit C code
# Quick way to see if a function hits C codecheck_function <-function(fn_name, pkg ="base") { fn <-get(fn_name, paste0("package:", pkg))if (is.primitive(fn)) {cat(fn_name, "is primitive (direct C)\n") } else { src <-deparse(body(fn))if (any(grepl("\\.Internal", src))) cat(fn_name, "uses .Internal\n")elseif (any(grepl("\\.Call", src))) cat(fn_name, "uses .Call\n")elsecat(fn_name, "is pure R\n") }}check_function("sum")check_function("paste")check_function("mean")check_function("which")
FAQ
Can I call .Internal() myself?
No. .Internal() can only be called from base R functions. If you try eval(.Internal(paste(...))) from user code, R blocks it for security and stability. Only .Call() is available for package authors.
Why does print(sum) show ".Primitive" instead of source code?
Primitive functions have no R source code, they're implemented entirely in C. The R interpreter recognizes the function name and jumps directly to compiled code without creating an execution environment.